前言
做陕西省赛ezpop时遇到的一个知识点
这里专门研究一下,本质上是对php gc回收机制的利用
简介
由于在php中,一个类的生命周期是这个页面所有代码执行完毕。我们可以理解为,每请求一次php的页面,就意味着我们想要从头执行一遍php代码渲染页面,在其中所用到的所有类都将在这个页面渲染结束后才会销毁,只有销毁的时候才会触发对应类的__destruct
函数,一旦在运行过程中出现报错,或者代码终止,__destruct
函数不会被触发,那么我们的反序列化漏洞将会利用不成功。
今天介绍的这个技巧被称为fast destruct
,便可以在unserialize
函数执行完后,立即触发我们的poc,从而使得反序列化漏洞利用稳定性提高。
原理
unserialize
函数反序列化代码的过程大概如下:(详细调试过程)
获取反序列化字符串–>根据类型进行反序列化—>查表找到对应的反序列化类–>根据字符串判断元素个数–>new出新实例–>迭代解析化剩下的字符串–>判断是否具有魔法函数__wakeup并标记—>释放空间并判断是否具有具有标记—>开启调用
根据上面的流程,我们可以发现,这个过程中是逐步对对象做解析的,而且解析过程中会同时去根据相应的魔法函数标记去调用魔法函数,
所以说,即使完整的反序列化最终失败了,但在这个过程中涉及到的对象仍然是可以正常出发魔法函数的调用的。fast_destruct的目的就是让完整的反序列化失败,再利用unserialize
运行失败后会对运行中已经创建出来类进行销毁这一特性,去提前触发对应类中的__destruct
函数。
引用一下大佬的解释:
- 在PHP中如果单独执行
unserialize()
函数,则反序列化后得到的生命周期仅限于这个函数执行的生命周期,在执行完unserialize()函数时就会执行__destruct()
方法 - 而如果将
unserialize()
函数执行后得到的字符串赋值给了一个变量,则反序列化的对象的生命周期就会变长,会一直到对象被销毁才执行析构方法
这里其实就是涉及到了php gc回收的机制:https://c1oudfl0w0.github.io/blog/2023/11/17/PHP-GC%E5%9B%9E%E6%94%B6/
本地测试
<?php
class A
{
public $info;
public $end = "1";
public function __destruct()
{
$this->info->func();
echo "A::des\n";
}
}
class B
{
public $znd;
public function __wakeup()
{
$this->znd = "exit();";
echo "B::wakeup\n";
}
public function __call($method, $args)
{
echo "B::call\n";
}
}
unserialize('O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"znd";N;}s:3:"end";s:1:"1";}');
上面是正常的序列化字符串,运行会得到B::wakeup B::call A::des
但是当我们把末尾的}
去掉,则会得到B::call A::des B::wakeup
而当我们在最后的;
后再加了一个;
,则会得到B::call A::des B::wakeup
这说明,当我们执行后面两个操作的时候,本该最先执行__wakeup()
方法变成最后执行的了
同样的代码我们来测试另一个例子
<?php
class A
{
public $info;
public $end = "1";
public function __destruct()
{
$this->info->func();
echo "A::des\n";
}
}
class B
{
public $znd;
public function __wakeup()
{
$this->znd = "exit();";
echo "B::wakeup\n";
}
public function __call($method, $args)
{
echo "B::call\n";
}
}
$a=unserialize('O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"znd";N;}s:3:"end";s:1:"1";}');
throw new Exception("Ciallo!");
正常的序列化字符串执行之后只返回了B::wakeup
和后面抛出的异常信息
但是当我们传入上述的那两个操作后,得到了B::call A::des B::wakeup
这整段输出信息和后面抛出的异常信息
可见最后抛出的异常会影响到前面除了__wakeup()
方法的内容的回显
而利用fast_destruct可以实现绕过