前言
最近遇到了这个知识点,网上找了好几篇这方面的好像都不太仔细,自己来总结下
0x01 序列化和反序列化
serialize()
所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。
序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字
如下面的:
<?php
class people{
public $name = 'lonmar';
protected $age = 99;
private $sex = 'man';
}
$lonmar = new people();
var_dump(serialize($lonmar));
// string(88) "O:6:"people":3:{s:4:"name";s:6:"lonmar";s:6:"<0x00>*<0x00>age";i:99;s:11:"<0x00>people<0x00>sex";s:3:"man";}"
对于对象,序列化后的格式为:
O:strlen(类名):类名:类的变量个数:{类型:长度:值;类型:长度:值…}
其他类型的数据序列化后的格式为:
String类型 :s:size:value;
Integer类型 :i:value;
Boolean类型 : b:value;
(保存1或0)
Null型 :N;
Array :a:size:{key definition;value definition}
对象中成员的形式,不同属性的差别主要体现在key上,而且key一定是string,直接给出模式,具体形式可以参考上面的示例:
public: s:size:value
size是string长度
protected: s:size+3:<0x00>*<0x00>value
这里<0x00>
是一个字符,十六进制为00
private: s:size+strlen(class_name)+2:<0x00>class_name<0x00>value
还有需要注意的点是;
分割不同字段}
结尾,这对反序列化很重要
unserialize()
unserialize() 对单一的已序列化的变量进行操作,将其转换回 PHP 的值
反序列化特点
- php在反序列化时,底层代码是以
;
作为字段的分隔,以}
作为结尾
这会造成,随便在序列化数据后添加一些无用字符,反序列化的时候也会被忽略,因为遇到了;}
会忽略后面的字符
-
unserialize根据
长度
判断内容,长度不对应的时候会报错
或者下面这个例子:
-
可以反序列化类中不存在的元素
0x02 字符逃逸
字符逃逸某种角度上和sql注入类似,都是通过构造闭合的方式构造payload,只不过字符逃逸构造闭合注意点在长度,因为闭合标志固定,都是;}
前面提到在unserialize的时候, 当你的字符串长度与所描述的长度不一样时就会报错.比如 s:3:"Tom"变成s:4:"Tom"或s:2:"Tom"就会报错. 可以通过拼接字符串的方式来使它不报错
所以字符逃逸又分为两类
- 字符变多
- 字符变少
字符变多
例子:
<?php
highlight_file(__file__);
function filter($str){
return str_replace('l', 'll', $str);
}
class person{
public $name = 'lonmar';
public $age = '100';
}
$test = new person();
$test = serialize($test);
echo "</br>";
print_r($test);
echo "</br>";
$test = filter($test);
print_r($test);
print_r(unserialize($test));
因为替换过后,实际长度为7,而描述长度为6,少读了一个r
所以失败
这种字符增多是反序列化失败是因为漏读了字符串的value,如果构造恶意的value,再故意漏读
如令$name='lonmar";s:3:"age";s:2:"35";}'
如果再进行替换,lonmar=>llonmar,后面的}
又读不到
再多几个l
,lllllllllllllllllllllonmar=>llllllllllllllllllllllllllllllllllllllllllonmar ,";s:3:"age";s:2:"35";}
就又读不到
只能读到lllllllllllllllllllllonmar
这样后面的;s:3:“age”;s:2:“35”;}就逃逸掉了,逃逸掉的字符串可以把原来后面的正常序列化数据提前闭合掉.(闭合条件";}
;s:3:"age";s:2:"35";}
长度是22 , 所以只需要22个l
如下:
<?php
function filter($str){
return str_replace('l', 'll', $str);
}
class person{
public $name = 'llllllllllllllllllllllonmar";s:3:"age";s:2:"35";}';
public $age = '100';
}
$test = new person();
$test = serialize($test);
var_dump($test);
$test = filter($test);
var_dump($test);
var_dump(unserialize($test));
这里类名做了替换people=>person 因为people里也有l
可以观察到age变成了35,
name不是llllllllllllllllllllllllllllllllllllllllllllonmar";s:3:"age";s:2:"35";}
而是 llllllllllllllllllllllllllllllllllllllllllllonmar
因为;s:3:“age”;s:2:“35”;}逃逸,之后终止标志变成了;s:3:“age”;s:2:“35”;}里的;}
后面的就被忽略了
字符减少
字符减少,反序列化的时候就会多读
<?php
highlight_file(__file__);
function filter($str){
return str_replace('ll', 'l', $str);
}
class person{
public $name = 'lonmar';
public $age = '100';
}
同样的,如果构造恶意的age,让反序列化的时候多读,把age一部分读进去 同样可以达到某种目的
正常的数据 O:6:"person":2:{s:4:"name";s:6:"lonmar";s:3:"age";s:3:"xxx";}
如果做替换,让也";s:3:"age";s:3:"
被读进name,再把xxx替换为;s:3:“age”;s:3:“100”;}
令$age=123";s:3:"age";s:3:"100";}
O:6:"person":2:{s:4:"name";s:47:"llllllllllllllllllllllllllllllllllllllllllonmar";s:3:"age";s:26:"123";s:3:"age";s:3:"111";}";}
多读的为 ";s:3:"age";s:26:"123
长度 21
构造(l*42)nmar, 就多吞了部分字符串,
name:
llllllllllllllllllllllllllllllllllllllllllonmar
=>
lllllllllllllllllllllonmar";s:3:"age";s:26:"123
age:
123";s:3:"age";s:3:"111";}
=>
111
总结
大致就像前面例子那样,根据字符增多或者减少,让该成员反序列化时,部分字符逃逸或者吞掉后面成员的部分字符达到一定目的
具体题目: https://blog.csdn.net/weixin_45551083/article/details/111084908