PHP反序列化
梳理一下学过的(反)序列化的知识点
概念:序列化就是使用serialize()将对象的用字符串的方式进行表示,反序列化是使用unserialize()将序列化的字符串,构造成相应的对象,反序列化是序列化的逆过程。 序列化的对象可以是class也可以是Array,string等其他对象
问题原因:漏洞的根源在于unserialize()
函数的参数可控。如果反序列化对象中存在魔术方法,而且魔术方法中的代码或变量用户可控,就可能产生反序列化漏洞
两种参考
①
看个序列化例子
1 |
|
对象序列化后的结构为:
O:6:"Class1":3:{s:1:"a";s:4:"data";s:4:"%00%00*b";s:4:"name";s:9:"%00Class1%00c";s:3:"num";}
1 |
|
- a是public类型的变量,s表示字符串,1表示变量名的长度,a是变量名。
- b是protected类型的变量,它的变量名长度为4,也就是b前添加了**%00%00。所以,protected属性的表示方式是在变量名前加上%00%00**。即:
%00%00*b
- c是private类型的变量,c的变量名前添加了**%00类名%00。所以,private属性的表示方式是在变量名前加上%00类名%00**。即:
%00Class1%00c
- 虽然Test类中有name方法,但是,序列化得到的字符串中,只保存了公有变量a,保护变量b和私有变量c,并没保存类中的方法。也可以看出,序列化不保存方法。
②
demo
1 |
|
对照看
1 |
|
1 |
|
对象类型:Class-O,Array-a。
变量和参数类型:string-s,int-i,Array-a,引用-R。
序列符号:参数与变量之间用分号(;)隔开,同一变量和同一参数之间的数据用冒号(:)隔开。
类型 | 结构 |
---|---|
String | s:size:value; |
Integer | i:value; |
Boolean | b:value;(保存1或0) |
Null | N; |
Array | a:size:{key definition;value definition;(repeated per element)} |
Object | O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)} |
Reference | R:2; |
三种访问控制的区别
public: 变量名
protected: \x00 + * + \x00 + 变量名(或 \00 + * + \00 + 变量名 或 %00 + * + %00 + 变量名)
private: \x00 + 类名 + \x00 + 变量名(或 \00 + 类名 + \00 + 变量名 或 %00 + 类名 + %00 + 变量名)
注:>=php v7.2 反序列化对访问类别不敏感(protected -> public)
反序列化对访问类别不敏感(protected -> public)是指在进行对象反序列化时,访问修饰符的级别会被忽略或绕过。
在面向对象编程中,访问修饰符(如
public
、protected
、private
)用于控制类的成员(字段、方法)的可访问性。protected
访问修饰符表示成员只能在当前类或其子类中访问,而public
访问修饰符表示成员可以在任何地方访问。当一个对象被序列化时,其状态被转换为字节流以便存储或传输。在对象反序列化过程中,字节流会被还原为对象的状态。但是,有时候反序列化操作可能会忽略对访问修饰符的检查,导致一些本应该是受保护的成员变成了公开的,即从
protected
变成了public
。这种行为可能会带来安全风险,因为本来受到访问限制的成员现在可以被任意访问和修改,可能会导致程序行为不一致或安全漏洞。因此,在进行对象反序列化时,需要特别注意对访问修饰符的敏感性,确保反序列化的对象状态不会违反设计的访问控制约束。
魔术方法
1 |
|
一些trick
__wakeup()失效
适用版本:PHP5<5.6.25 或 PHP7<7.0.10
当序列化字符串中,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup()的执行。
实际序列化内容为:
1 |
|
如:
1 |
|
PHP7.3
Serialize
特性:O
改为 C
(需要利用内置了Serializable
接口的类)
若只需绕过wakeup这就ok了
例如
1 |
|
改为
1 |
|
需要用属性命令执行
demo
1 |
|
payload
1 |
|
用下面绕过__wakeup()
1 |
|
举个例子
愚人杯3rd [easy_php]
考点:PHP7.3 __wakeup绕过,ArrayObject内置类
从上面我们已知可以使用C进行绕过wakeup,但这样有一个缺点,就是你把O改为C后是没办法有属性的,那假如需要用属性命令执行就不行了
这种情况我们可以用内置类ArrayObject
1 |
|
这个题目很明显就是要执行system方法,然后不可以以O\a
打头,假如不ban掉a的话,我们可以在a数组里面放上我们的恶意对象,也可以反序列化,但是这里都去掉了,所以回到上面说的那个ArrayObject,他是C开头的,并且可以绕过O,然后还可以带属性反序列化,符合条件,因此可以构造payload:
1 |
|
payload
1 |
|
绕过preg_match() 匹配的关键字
PHP低版本
可使用+
,<
绕过正则,如:
1 |
|
1 |
|
普通字符串
PHP序列化中存在序列化类型 S
,相较于小写的 s
,大写 S
是escaped字符串,会将 \xx
形式作为一个16进制字符处理,如:D
的十六进制是 44
,所以把 Demo
替换为 \44emo
即可绕过。
/^O:\d+/
1 |
|
1 |
|