[Web 安全] PHP 反序列化漏洞 —— PHP 序列化 反序列化

news/2025/2/26 8:55:04

关注这个专栏的其他相关笔记:[Web 安全] 反序列化漏洞 - 学习笔记-CSDN博客

0x01:PHP 序列化 — Serialize

序列化就是将对象的状态信息转化为可以存储或传输的形式的过程,在 PHP 中,通常使用 serialize() 函数来完成序列化的操作。

下面笔者直接简要点列出各个数据类型序列化之后的结果,不信的崽崽可以自己跑一下:

php"> echo serialize(null);       // N;
 echo serialize(123);        // i:123;   => int 类型的值为 123
 echo serialize(123.3);      // d:123.3; => double 类型的值为 123.3
 echo serialize(true);       // b:1;     => Boolean 类型的值为 True
 echo serialize("Blue17");   // s:6:"Blue17"; => String 类型的值为 Blue17
 echo serialize(array("Blue17", 17, null)); 
 // a:3:{i:0;s:6:"Blue17";i:1;i:17;i:2;N;}

0x0101:序列化结果解析 — Array 类型

这部分笔者就简单分析一下上面 Array 类型序列化后的结果:

php"> echo serialize(array("Blue17", 17, null)); 
 // a:3:{i:0;s:6:"Blue17";i:1;i:17;i:2;N;}
 ​
 /*
 a:3               => array 中有三个元素
 i:0;s:6:"Blue17"  => index 为 0 的地方是一个长度为 6 的 String 类型的元素,值为 Blue17
 i:1;i:17;         => index 为 1 的地方是一个 int 类型的元素,值为 17
 i:2;N;            => index 为 2 的地方是一个 Null 类型的元素。
 */

0x0102:序列化结果解析 — 类

类的序列化结果基本大差不差,但是不同 “访问类型” 的变量序列化的结果有很大差异,这部分笔者就按照 “访问类型” 进行分类,并对每个单独进行讲解。

1. 类序列化结果解析 —— Public 型

这里我们开始进入正规,讲讲最常见的类的序列化结果,先来一个最常见的试试水:

php"> <?php
 ​
 class demo {
     public $var1;               // 这个变量没有赋值
     public $var2 = "Blue17";    // 这个变量赋予了字符串类型的值
     var $var3 = 17;          // 虽然修饰符是 var 但其实还是 public 类型的
 ​
     function printVar($var) {
         $localVar = $var;    // 类方法中的局部变量
         echo $localVar;
     }
 }
 ​
 // O:4:"demo":3:{s:4:"var1";N;s:4:"var2";s:6:"Blue17";s:4:"var3";i:17;}
 echo serialize(new demo(array(123)));

下面我们仔细分析一下结果,可以看到,它序列化的结果基本全是变量,类中方法其实是没有被序列化的,类中的局部变量也没有被序列化

php"> O:4:"demo":3:{s:4:"var1";N;s:4:"var2";s:6:"Blue17";s:4:"var3";i:17;}
 ​
 // O:4:"demo":3 => Object 对象是一个 4 字的叫 demo,其中有 3 个属性(变量)
 // s:4:"var1";N; => 属性名称占 4 字节,叫 var1 其值是 Null 类型
 // s:4:"var2";s:6:"Blue17" => 属性名称占 4 字节,叫 var2,其值是 String 类型,长度为 6 内容是 Blue17
 // s:4:"var3";i:17; => 属性名称占 4 字节,叫 var3,其值是 int 类型,值为 17。

2. 类序列化结果解析 —— Protected 型

下面我们来看看如果类的属性中混入了 Protect 型的变量它序列化的结果长啥样吧:

php"> <?php
 ​
 class demo {
     protected $var1;               // 这个变量没有赋值
     protected $var2 = "Blue17";    // 这个变量赋予了字符串类型的值
     protected $var3 = 17;          // 虽然修饰符是 var 但其实还是 public 类型的
 }
 ​
 echo serialize(new demo()) . "\n";
 // O:4:"demo":3:{s:7:"*var1";N;s:7:"*var2";s:6:"Blue17";s:7:"*var3";i:17;}
 ​
 echo urlencode(serialize(new demo()));
 // O%3A4%3A%22demo%22%3A3%3A%7Bs%3A7%3A%22%00%2A%00var1%22%3BN%3Bs%3A7%3A%22%00%2A%00var2%22%3Bs%3A6%3A%22Blue17%22%3Bs%3A7%3A%22%00%2A%00var3%22%3Bi%3A17%3B%7D

下面我们仔细分析一下结果,这里笔者特意对它序列化后的结果做了一个编码,因为里面其实有一些不可见的字符,不编码是看不出来的,大部分内容其实与 Public 一致,但是被 protected 修饰的变量序列化后的内容就有很大不同了:

php"> O:4:"demo":3:{s:7:"*var1";N;s:7:"*var2";s:6:"Blue17";s:7:"*var3";i:17;}
 ​
 // s:7:"*var1";N; => 属性名称占 7 个字节?怎么数着只有 5 个?
 ​
 // 这个是 URL 编码后的内容:s%3A7%3A%22%00%2A%00var1%22%3BN%3B
 // 这个是简单解码后的样子:s:7:"%00*%00var1";N;

如上,可以发现,Protected 属性序列化后明面看只有 *var1 这样,但其实 * 两边其实还藏了两个 ASCII 码值为 0 的字符 (这也引出后面一个 Bug,在尝试构造反序列化值得时候,不建议通过直接复制就篡改被 Protected 或者 Private 修饰的值,因为你复制得内容一般都不全,会丢掉这个特殊的 %00)。

3. 类序列化结果解析 —— Private 型

下面我们来看看如果类的属性中混入了 Private 型的变量它序列化的结果长啥样吧:

php"> <?php
 ​
 class demo {
     private $var1;               // 这个变量没有赋值
     private $var2 = "Blue17";    // 这个变量赋予了字符串类型的值
 }
 ​
 echo serialize(new demo()) . "\n";
 // O:4:"demo":2:{s:10:"demovar1";N;s:10:"demovar2";s:6:"Blue17";}
 ​
 echo urlencode(serialize(new demo()));
 // O%3A4%3A%22demo%22%3A2%3A%7Bs%3A10%3A%22%00demo%00var1%22%3BN%3Bs%3A10%3A%22%00demo%00var2%22%3Bs%3A6%3A%22Blue17%22%3B%7D

下面我们仔细分析一下结果,这里笔者特意对它序列化后的结果做了一个编码,因为里面其实有一些不可见的字符,不编码是看不出来的,大部分内容其实与 Public 一致,但是被 Private 修饰的变量序列化后的内容就有很大不同了:

php"> O:4:"demo":2:{s:10:"demovar1";N;s:10:"demovar2";s:6:"Blue17";}
 ​
 // s:10:"demovar1";N; => 属性名称占 10 个字节?怎么数着只有 8 个?
 ​
 // 这个是 URL 编码后的内容:s%3A10%3A%22%00demo%00var1%22%3BN%3B
 // 这个是简单解码后的样子:s:10:"%00demo%00var1";N; => %00 算一位,数一数,刚好 10 位

如上,可以发现,Private 属性序列化后明面看只有 demovar1 这样,但其实类名两边其实还藏了两个 ASCII 码值为 0 的字符,所以其真实格式为(URL 编码后的哈,不编码的话 ASCII 值为 0 的其实是不可见字符) %00类名%00变量名

0x02:PHP 反序列化 — Unserialize

以下是反序列化相关的几个特性:

  • 反序列化就是将序列化得到的字符串转化为一个对象的过程。

  • 反序列化生成的对象成员属性值由被反序列化的字符串决定,与原来类预定义的值无关。

  • PHP 中通过 unserialize() 函数进行反序列化,序列化使用 serialize() 函数。

  • 反序列化不触发类的成员方法,需要被调用方法后才会被触发。(不一定,这个后面讲)

下面笔者就以 Public 型的类为例,讲解一下反序列化的用处(另外两种类型,流程一致,但是要特别注意 %00 到底有没有被复制过去,如果报错了,一般就是这个的问题)。

0x0201:反序列化 —— 正常流程

首先是比较简单的 Public 型类的反序列化,我们先创建一个类,假设叫 Note (笔记)类吧,然后我们写笔记就要实例化这个类,然后往这个类的对象里写东西,代码如下:

php"><?php

class Note {
    public $title;   // 笔记标题
    public $content; // 笔记内容

    // 记录标题 & 内容
    function write($title, $content) {
        $this -> title = $title;
        $this -> content = $content;
    }

    // 读取标题 & 内容
    function read() {
        echo "Title: " . $this -> title . "\n";
        echo "Content: " . $this -> content . "\n";
    }
}

// 实例化笔记类
$note = new Note();
// 往笔记里写东西
$note -> write("Hello, World!!", "Today Is a Nice Day!!");

如上,我们已经往笔记里写东西了,写了你要保存吧,可是你是个对象你咋保存?这时就可以使用序列化,把 $note 这个对象里的内容序列化然后存储在一个文件里:

php">// 保存 $note 笔记里的东西
echo serialize($note); // O:4:"Note":2:{s:5:"title";s:14:"Hello, World!!";s:7:"content";s:21:"Today Is a Nice Day!!";}

OK,保存了你过段时间得看吧,给你看下面这个东西你又看不懂是不是:

php">O:4:"Note":2:{s:5:"title";s:14:"Hello, World!!";s:7:"content";s:21:"Today Is a Nice Day!!";}

这个时候就需要用我们软件进行反序列化然后再调用 read 方法了是吧:

php">// 保存 $note 笔记里的东西
$save = serialize($note); // O:4:"Note":2:{s:5:"title";s:14:"Hello, World!!";s:7:"content";s:21:"Today Is a Nice Day!!";}

// 查看保存的内容
$raw = unserialize($save); // 对序列化的内容进行反序列化
$raw -> read();

如上,可以看到,通过反序列化被保存的值,我们成功还原了用户笔记里写的东西。下面是整个测试的源码(这里笔者特别提醒,反序列化的环境中要有序列化的那个类,不然即使反序列化了也是无法执行类的方法的):

php"><?php

// 创建笔记类
class Note {
    public $title;   // 笔记标题
    public $content; // 笔记内容

    // 记录标题 & 内容
    function write($title, $content) {
        $this -> title = $title;
        $this -> content = $content;
    }

    // 读取标题 & 内容
    function read() {
        echo "Title: " . $this -> title . "\n";
        echo "Content: " . $this -> content . "\n";
    }
}

// 实例化笔记类
$note = new Note();
// 往笔记里写东西
$note -> write("Hello, World!!", "Today Is a Nice Day!!");

// 保存 $note 笔记里的东西
$save = serialize($note); // O:4:"Note":2:{s:5:"title";s:14:"Hello, World!!";s:7:"content";s:21:"Today Is a Nice Day!!";}

// 查看保存的内容
$raw = unserialize($save); // 对序列化的内容进行反序列化
$raw -> read(); // 执行类的成员方法

0x0202:反序列化 —— 异常流程

我们继续假设,我们刚刚是本地的,假设你写了笔记,然后你要上传,那你上传服务端的序列化的内容就是下面这个:

php">O:4:"Note":2:{s:5:"title";s:14:"Hello, World!!";s:7:"content";s:21:"Today Is a Nice Day!!";}

假设,攻击者截获了这个内容,按照 PHP 序列化的格式自己改了一下(主要是改内容和长度):

php">O:4:"Note":2:{s:5:"title";s:14:"Hello, World!!";s:7:"content";s:25:"Today Is NOT a Nice Day!!";}

如上,我们添加了一个单词 Not ,然后修改了长度,然后我们 “发送” 到服务端,让他读一下,代码如下:

php"><?php

// 创建笔记类
class Note {
    public $title;   // 笔记标题
    public $content; // 笔记内容

    // 记录标题 & 内容
    function write($title, $content) {
        $this -> title = $title;
        $this -> content = $content;
    }

    // 读取标题 & 内容
    function read() {
        echo "Title: " . $this -> title . "\n";
        echo "Content: " . $this -> content . "\n";
    }
}
// 接收的信息
$receive = 'O:4:"Note":2:{s:5:"title";s:14:"Hello, World!!";s:7:"content";s:25:"Today Is NOT a Nice Day!!";}';

$raw = unserialize($receive); // 对序列化的内容进行反序列化
$raw -> read(); // 调用读方法

如上,可以看到,结果就这样被修改了。这就是前面介绍的反序列化的一个特性 “反序列化生成的对象成员属性值由被反序列化的字符串决定,与原来类预定义的值无关。”,也是我们后面 “反序列化漏洞的依据”。


http://www.niftyadmin.cn/n/5868429.html

相关文章

React七Formik

Formik是一个专为React构建的开源表单库。它提供了一个易于使用的API来处理表单状态管理&#xff0c;表单验证以及表单提交。Formik支持React中的所有表单元素和事件&#xff0c;可以很好地与React生态系统中的其他库集成。同时&#xff0c;Formik还提供了一些高级功能&#xf…

github 推送的常见问题以及解决

文章目录 git add 的时候问题1为什么会发生这种情况&#xff1f;Git 的警告含义如何解决&#xff1f;1. **保持 Git 的默认行为&#xff08;推荐&#xff09;**2. **禁用自动转换**3. **仅在工作目录中禁用转换**4. **统一使用 LF&#xff08;跨平台开发推荐&#xff09;** git…

2.25力扣每日一题--设计内存分配器

2502. 设计内存分配器 - 力扣&#xff08;LeetCode&#xff09; 一&#xff1a;JAVA Allocator(int n)函数&#xff1a;类构造器 int allocate(int size, int mID)函数&#xff1a;输入&#xff0c;待插入块的大小size,插入内容mid / 输出&#xff0c;插入位置块的起始位置…

【redis】数据类型之Bitfields

Redis的Bitfields&#xff08;位域&#xff09;与Bitmaps一样&#xff0c;在Redis中并不是一种独立的数据类型&#xff0c;而是一种基于字符串的数据结构&#xff0c;用于处理位级别的操作。允许用户将一个Redis字符串视作由一系列二进制位组成的数组&#xff0c;并对这些位进行…

Linux 基本开发工具的使用(yum、vim、gcc、g++、gdb、make/makefile)

文章目录 Linux 软件包管理器 - yum理解什么是软件包和yum如何查看/查找软件包如何安装软件如何实现本地机器和云服务器之间的文件互传如何卸载软件 Linux 编辑器 - vim 的使用vim 的基本概念vim 的基本操作vim 命令模式各命令汇总vim 底行模式各命令汇总vim 的简单配置 Linux …

关于<<DeepSeek-R1:通过强化学习激励大语言模型的推理能力>>的解读

今日关于<<DeepSeek-R1:通过强化学习激励大语言模型的推理能力>>这篇文章很火, DeepSeek-R1:通过强化学习激励大语言模型的推理能力-CSDN博客 因为是专业文章很多小伙伴看不懂,那么今天我整理了一个解读文章,希望对你有所帮助: 这篇论文主要介绍了一种通过…

Java与NoSQL数据库的集成与优化

Java与NoSQL数据库的集成与优化 在现代企业应用中&#xff0c;NoSQL数据库因其灵活的数据模型、高可扩展性和高性能等特点&#xff0c;广泛应用于大数据处理、实时分析、社交网络等领域。与此同时&#xff0c;Java作为一种广泛使用的编程语言&#xff0c;也在与NoSQL数据库的集…

使用串口工具实现tcp与udp收发

1、使用串口工具实现tcp收发 2、使用串口工具实现udp收发