PHP反序列化
本文最后更新于:2022年9月26日 下午
PHP反序列化
序列化(serialize)就是将对象转换为字符串。反序列化(unserialize)则相反,数据的格式的转换对象的序列化利于对象的保存和传输,也可以让多个文件共享对象
访问控制
PHP 对属性或方法的访问控制,是通过在前面添加关键字 public(公有),protected(受保护)或 private(私有)来实现的。
public(公有):公有的类成员可以在任何地方被访问。
protected(受保护):受保护的类成员则可以被其自身以及其子类和父类访问。
private(私有):私有的类成员则只能被其定义所在的类访问。
类
有类时会触发 魔术方法
那什么时魔术方法呢?PHP中把两个下划线开头的方法称为魔术方法(Magic methods)
serialize() 函数会检查类中是否存在一个魔术方法。如果存在,该方法会先被调用,然后才执行序列化操作。
魔术方法包括:
魔术方法 | 用处 |
---|---|
__construct() | 实例化类时自动调用 |
__destruct() | 类对象使用结束时自动调用 |
__set() | 在给未定义的属性赋值时自动调用 |
__get() | 调用未定义的属性时自动调用 |
__isset() | 使用 isset() 或 empty() 函数时自动调用 |
__unset() | 使用 unset() 时自动调用 |
__sleep() | 使用 serialize 序列化时自动调用 |
__wakeup() | 使用 unserialize 反序列化时自动调用 |
__call() | 调用一个不存在的方法时自动调用 |
__callStatic() | 调用一个不存在的静态方法时自动调用 |
__toString() | 把对象转换成字符串时自动调用 |
__invoke() | 当尝试把对象当方法调用时自动调用 |
__set_state() | 当使用 var_export() 函数时自动调用,接受一个数组参数 |
__clone() | 当使用 clone 复制一个对象时自动调用 |
__debugInfo() | 使用 var_dump() 打印对象信息时自动调用 |
简单的序列化:
序列化后各个字符串的含义:
PHP反序列化漏洞原理
序列化和反序列化本身没有问题,但是如果反序列化的内容是用户可以控制的,且后台不正当的使用了PHP中的魔法函数,就会导致安全问题。当传给unserialize()
的参数可控时,可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。
例子
CTFHub中 2020-网鼎杯-青龙组-Web-AreUSerialz
代码审计
1 |
|
首先代码审计就是看两点,一有无漏洞,二有无可控变量。
我们发现源代码最后76行有可控变量str通过get传参,并且发现有unserialize反序列化。
我们再看到 flag.php 是在这个高亮化文件里面的,并且发现有class(类):
1 |
|
在反序列化后,相当于重新生成了一个对像,这个对象在程序结束时 析构执行_destruct()//第58行
如果op值为2则强制将op的值变为1,content值为空,调用process函数。//第20行
如果op值为1,则进入“写”函数;如果op值为2,则进入“读”函数。
这里我们需要读取到flag.php中的答案所以需要调用“读”函数//第45行
如果filename有值,则file_get_contents()函数把整个文件读入一个字符串中,如果给filename赋值为flag.php 那么我们就能读出flag了。
但是在destruct函数中进行了判断把2强制转换成了1:
1 |
|
所以我们这里需要知道,用三个等号时,除了两个变量的值相同外,还必须这两个变量的类型相同,而用两个等号时,只需要两个变量值相同。
我们构造payload时,构造op=‘ 2’字符串,则op=‘2’就不成立,此时op就成了我们自己设置的值。然后通过process()函数调用后:
1 |
|
因为这里为 op == “2”为弱类型对比只需要值相等就能调用“读”函数。就能读出flag.php文件了。
构造payload
序列化出来的payload为:
1 |
|
注意这里要把protect改为public才是公有的,并且要将FileHandler用new实例化。