当前位置:首页>php>PHP 反序列化

PHP 反序列化

  • 2026-08-24 08:36:18
PHP 反序列化

一、PHP 反序列化漏洞基础

1.1PHP 序列化

  概念:序列化:是将变量转换为可保存或传输的字符串的过程;实现函数是 serialize()反序列化:就是在适当的时候把这个字符串再转化成原来的变量使用,就是序列化的逆过程。实现函数是 unserialize()序列化和反序列化结合起来,可以轻松地存储和传输数据,使程序更具维护性。

  序列化格式:

类型
结构
Object
O:length:class name:attribute number:{attr1;value1;attr2;value2;}
Array
a:number:{key1;value1;key2;value2;}
String
s:length:value;
Integer
i:value;
Boolean
b:value; //(0=False,1=True)
Double
d:value;
pointer reference
R:number

  例子:对象的序列化

  to

  to

  对象字段(属性)名的序列化规则:var 和 public:var 和 public 声明的字段都是公共字段,因此它们的字段名的序列化格式是相同的。公共字段的字段名按照声明时的字段名进行序列化,但序列化后的字段名中不包括声明时的变量前缀符号 $。protected:声明的字段为保护字段,在所声明的类和该类的子类中可见,但在该类的对象实例中不可见。保护字段的字段名在序列化时,字段名前面会加上\0*\0 的前缀。这里的\0 表示 ASCII 码为 0 的字符,属于不可见字符,因此该字段的长度会比可见字符长度大 3。private:声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。私有字段的字段名在序列化时,字段名前面会加上\0<declared classname>\0 前缀。这里 <declared classname> 表示的是声明该私有字段的类的类名,而不是被序列化的对象的类名。因为声明该私有字段的类不一定是被序列化的对象的类,而有可能是它的祖先类。

  数组的序列化:

  to

  to

  格式由类型标识 + 数据长度 + 内容组成,核心标识:

  • s:n:"xxx" :字符串,n 为字符长度,如 s:4:"name"

  • i:n :整数,n 为数值,如 i:20

  • a:n:{...} :数组,n 为元素个数,大括号内为键值对

  • O:n:"类名"m:{...} :对象,n 为类名字符数,m 为属性个数

  • N; :NULL

  • b:0/1; :布尔值(假/真)

  私有属性特殊格式:私有属性序列化后,属性名会拼接 \0 类名\0 (不可见空字符),长度包含空字符,如 User 类的 $pwd 会变成 \0User\0pwd (长度 8)。

1.2 PHP 反序列化漏洞

  反序列化漏洞:也叫对象注入,就是当程序在进行反序列化时,会自动调用一些函数,但是如果传入函数的参数可以被用户控制的话,用户可以输入一些恶意代码到函数中,从而导致反序列化漏洞。可以理解为程序在执行 unserialize()函数是,自动执行了某些魔术方法(magic method),而魔术方法的参数被用户所控制(通过控制属性来控制参数),这就会产生安全问题。

  漏洞利用条件:

  1. unserialize()函数的参数可控。

  2. 存在可利用的魔术方法。

  3. 魔术方法内调用了危险函数,且危险函数的参数可控(由对象的属性赋值)。

  出现场景:

  1. 代码审计;

  2. 服务器源码泄露后挖掘漏洞;

  3. CTF-WEB 题目。

1.3 属性赋值

  要利用反序列化漏洞,必须向 unserialize()函数传入构造的序列化数据(定义合适的属性值)。那么,如何构造序列化数据呢?直接写序列化数据显示是不合适的,因为序列化数据不符合人类直观,很容易出错。实际上,都是利用 serialize()函数来生成序列化数据的。一般输出都会使用 urlencode 函数对 URL 编码然后输出

  进行 URL 编码的原因:

  1. 原始的序列化数据可能存在不可见字符;

  2. 如果不进行编码,最后输出的结果是片段的,不是全部的,会有类似截断导致结果异常,所以需要进行 url 编码。

  对属性赋值主要有 3 种方法:

  1. 直接在属性中赋值:优点是方便,缺点是只能赋值字符串

  to

  1. 外部赋值:优点是可以赋值任意类型的值,缺点是只能操作 public 属性。

  to

  1. 构造方法赋值(万能方法):优点是解决了上述的全部缺点,缺点是有点麻烦

  to

1.4 魔术方法

  魔术方法是一种特殊的方法,在某些情况下会自动调用。魔术方法的命名是以两个下划线开头的,常见的魔术方法有:

  to

二、POP 链

2.1 概念

  POP 链:POP(面向属性编程)链是指从现有运行环境中寻找一系列的代码或指令调用,然后根据需求构造出一组连续的调用链。反序列化利用就是要找到合适的 POP 链。其实就是构造一条符合原代码需求的链条,去找到可以控制的属性或方法,从而构造 POP 链达到攻击的目的。寻找 POP 链的思路:

  1. 寻找 unserialize()函数的参数是否可控,运用反推法,寻找起始与重点函数;

  2. 寻找反序列化想要执行的目标函数,重点寻找魔术方法(比如__wakeup()和__destruct());

  3. 一层一层地研究目标在魔术方法中使用的属性和调用的方法,看看其中是否有我们可控的属性和方法;

  4. 根据我们要控制的属性,构造序列化数据,发起攻击。

2.2 畸形序列化字符串

2.2.1 认识畸形序列化字符串

  畸形序列化字符串就是故意修改序列化数据,使其与标准序列化数据存在个别字符的差异,达到绕过一些安全函数的目的。应用领域:

  1. 绕过__wakeup()

  2. 快速析构(fast destruct):绕过过滤函数,提前执行__destruct

2.2.2 绕过__wakeup

  由于使用 unserialize()函数后会立即触发__wakeup,为了绕过__wakeup 中的安全机制,可以用修改属性数量的方式绕过__wakeup 方法。受影响版本:

  php5.0.0 - php5.6.25 php7.0.0 - php7.0.10

  绕过方法:1.反序列化时,修改对象的属性数量,将原数量 +n,那么__wakeup 方法将不再调用。2.增加真实属性的个数。

2.3.3 快速析构

  快速析构的原理:当 php 接收到畸形序列化字符串时,PHP 由于其容错机制,依然可以反序列化成功。但是,由于你给的是一个畸形的序列化字符串,总之他是不标准的,所以 PHP 对这个畸形序列化字符串得到的对象不放心,于是 PHP 就要赶紧把它清理掉,那么就触发了他的析构方法(__destruct())。应用场景:某些题目需要利用__destruct 才能获取 flag,但是__destruct 是在对象被销毁时才触发(执行顺序太靠后),__destruct 之前会执行过滤函数,为了绕过这些过滤函数,就需要提前触发__destruct 方法。畸形字符串的构造1.改掉属性的个数2.删掉结尾的}

三、PHP 指针与反序列化字符逃逸

1.1 指针

  用&符号可以进行指针引用,类似于 C 语言中的指针。

2. 反序列化字符逃逸

2.1 字符逃逸

  字符逃逸本质:对序列化字符串进行不等长的字符串替换,导致本来属于普通字符串的一部分字符串变成了序列化的一部分,或者导致本来不属于字符串的一部分变成了字符串的一部分,进而造成了序列化数据的错乱,导致了对象注入。思路:

  1. 写出基本序列化

  2. 写出注入的对象

  3. 分析是长到短还是短到长的替换,决定要把对象注入到什么地方

  4. 算清楚替换的差值,计算需要吃掉或挤出(逃逸)的字符串的长度,保证这个长度是替换的差值的整数倍,如果不能保证,加字符串

  5. 构造替换,对象注入。

  类型:长到短的替换:在第一个元素进行替换,进而吃掉第二个元素的约束,第二个元素就逃逸出来了。短到长的替换:直接在替换位点后面跟上注入的对象,注入对象就可以逃逸出来。

  ‍

‍

最新文章

随机文章