ISCC2026部分web题wp
字数 8186
更新时间 2026-05-22 12:33:13
ISCC 2026 部分 Web 题漏洞利用与知识点教学文档
前言
本教学文档基于 ISCC 2026 CTF 竞赛中的部分 Web 题目(Write-Up)整理而成,旨在深入解析其中涉及的网络安全漏洞原理、利用手法及防护思路。文档内容涵盖 PHP 魔术哈希、SSRF、反序列化字符逃逸、Java 路径解析缺陷、SSTI 等多个经典漏洞类型。
题目一:值班邮件台
漏洞类型
- Cookie 身份伪造
- 任意文件读取 (Arbitrary File Read)
- PHP 类型混淆魔术哈希漏洞 (Magic Hash)
- 服务端请求伪造 (SSRF)
解题步骤与原理分析
1. 身份绕过
- 初始状态:访问后台
http://39.105.213.28:49103/admin.php返回提示Only admin can access this page.。 - 利用方法:通过修改 Cookie 为
mail_user=admin; mail_role=admin来伪造管理员身份。这是一种典型的会话或身份标识伪造漏洞,系统仅通过客户端提供的 Cookie 值判断用户权限,未在服务端进行有效验证。
2. 信息收集与任意文件读取
- 在获得后台访问权限后,发现
download.php端点存在文件下载功能,参数为file。 - 通过读取联调说明文件 (
preview-readme.txt),获得了关键信息:- 后台预览器仅用于查看内部诊断结果。
- 双人复核逻辑在
admin.php。 - 诊断地址命名规则在
route-index.txt。
- 漏洞利用:尝试路径穿越,成功读取
route-index.txt和admin.php的源代码。这通常是因为download.php在拼接文件路径时,未对用户输入的file参数进行严格的过滤或白名单限制,导致可以读取服务器上的任意文件(如../../../etc/passwd),本例中通过?file=admin.php直接获取了后台逻辑源码。
3. PHP 魔术哈希绕过
- 在
admin.php源码中,发现“双人复核”逻辑:$tokenA = (string)($_POST['token_a'] ?? ''); $tokenB = (string)($_POST['token_b'] ?? ''); $h1 = md5($tokenA); $h2 = md5($tokenB); if ($h1 == $h2 && $h1 !== $tokenB) { // 校验通过 } - 漏洞原理:
==是 PHP 的松散比较,md5计算以0e开头的字符串时,结果会被科学记数法解释为 0。当两个不同的字符串经过md5后都产生0e开头的哈希值时,0e123... == 0e456...在松散比较下均为0 == 0,结果为true。同时,!==确保了两个输入字符串本身不同。 - 常用 Payload:
token_a=240610708与token_b=QNKCDZO。md5('240610708')=0e462097431906509019562988736854md5('QNKCDZO')=0e830400451993494058024219903391
4. SSRF 获取 Flag
- 通过魔术哈希绕过复核后,可向
target_url参数发起请求。 - 从
route-index.txt得知内部路由final -> /internal/report?view=flag&slot=last。 - 构造 SSRF Payload:
target_url=http://127.0.0.1/internal/report?view=flag&slot=last - 注意点:直接在表单中提交该 URL,避免使用某些工具(如 Yakit)将
&slot=last错误识别为新的参数。此漏洞允许攻击者以服务器身份访问内部网络服务,从而读取本应无法外泄的内部信息(Flag)。
题目二:灵感笔记
漏洞类型
- 不安全的反序列化 (Insecure Deserialization)
- 信息泄露 (Information Disclosure)
解题步骤与原理分析
1. 信息收集
- 注册登录后,通过浏览器开发者工具查看前端 JS 代码 (
main.js),发现一个隐藏的 API 端点/api/admin/hint。 - 访问该端点获得提示,得知核心 API 为
POST /api/v1/project/detail,需要project_id参数。
2. 关键操作与信息泄露
- 尝试访问首页存在的“非常重要的笔记”链接,其路径为
/project/flag-project-001。访问后,服务器在 Cookie 中设置了新的会话标识。 - 使用此新 Cookie 访问用户反馈接口
/feedback,并带上请求project/detailAPI 时返回的trace_id。 - 漏洞利用:反馈接口返回了详细的错误信息,其中包含了一个经过编码的
stack_trace字段。该字段是 Pythonpickle序列化后的对象经过十六进制表示的字符串。 - 获取 Flag:将
stack_trace的十六进制字符串解码后,得到pickle序列化数据。反序列化该数据或直接分析其结构,可提取出包含 Flag 的 Python 对象。在本例中,解码后得到类似b'\x80\x04\x95...FLAG_OBJECT...ISCC{...}'的数据,从中即可找到 Flag。 - 根本原因:系统在错误处理时,将包含敏感信息的内部对象(包括 Flag)的序列化数据直接返回给客户端,造成了严重的信息泄露。
题目三:逆向穿越
漏洞类型
- Java 路径遍历 (Path Traversal) 与 路径解析逻辑漏洞
前置知识:Java Path.resolve()
Path.resolve()用于拼接路径。关键规则:如果传入的参数是绝对路径,则会直接返回该绝对路径,而忽略之前的路径前缀。- 示例:
Paths.get("/app/resources/config").resolve("infra")->/app/resources/config/infraPaths.get("/app/resources/config").resolve("/app/application.yml")->/app/application.yml(前面部分被覆盖)
解题步骤与原理分析
1. 信息收集
- 访问给定的两个配置文件路径,从
/config/app/dev/application.yml中获得提示:真正的密钥在/app/application.yml。
2. 尝试与绕过
- 直接访问
/app/application.yml返回 404。 - 尝试使用
../等进行常规路径穿越,均被拦截。 - 漏洞发现:阅读后端
ConfigController源码(通过构造特殊路径读取,如/config/x/%5c/app%2fsrc%2fmain%2fjava%2fcom%2fctf%2fchallenge%2fConfigController.java),理解其过滤逻辑。
3. 源码分析与漏洞构造
关键代码片段:
@GetMapping("/config/{app}/{profile}/{filename}")
public ResponseEntity<String> getConfig(@PathVariable String app, @PathVariable String profile, @PathVariable String filename) {
// 1. 过滤 `..`
if (app.contains("..") || filename.contains("..")) { ... }
if (profile.contains("../")) { ... }
// 2. URL解码
String decodedProfile = URLDecoder.decode(profile, StandardCharsets.UTF_8.toString());
// 特殊逻辑:包含 `..` 但不包含 `..\` 才拦截
if (decodedProfile.contains("..") && !decodedProfile.contains("..\")) { ... }
// 3. 关键替换:将反斜杠替换为正斜杠
String normalizedPart = decodedProfile.replace("\", "/");
// 4. 路径拼接
Path base = Paths.get(BASE_PATH).toAbsolutePath(); // BASE_PATH = "/app/resources/config"
Path filePath = base.resolve(app).resolve(normalizedPart).resolve(filename).normalize();
// 5. 沙箱校验:必须在 /app 目录下
Path sandbox = Paths.get(SANDBOX_PATH).toAbsolutePath().normalize(); // SANDBOX_PATH = "/app"
if (!filePath.startsWith(sandbox)) { return ...; }
}
漏洞利用链:
- 目标:读取
/app/application.yml。 - 构造参数:
app:application.ymlprofile:%5c(URL 编码的反斜杠\)filename:app/application.yml
- 处理流程:
profile解码后变为\。- 经过
normalizedPart = decodedProfile.replace("\", "/")处理,\被替换为/,因此normalizedPart = "/"。 - 路径拼接:
base.resolve(app).resolve(normalizedPart).resolve(filename)
=/app/resources/config+/application.yml+/+/app/application.yml - 由于
resolve("/")的参数是绝对路径/,根据Path.resolve()的规则,它会直接返回根路径/,覆盖掉前面的/app/resources/config/application.yml。 - 继续
resolve("app/application.yml"),得到最终路径:/app/application.yml。
- 通过校验:最终路径
/app/application.yml以/app开头,通过沙箱校验,读取成功。
4. 深入利用
- 成功读取
/app/application.yml后,发现 Spring Boot Actuator 的env端点路径为/internal-monitor-xyz123,且FLAG环境变量被脱敏。 - 从
env端点信息中,找到备份文件路径配置:system.diagnostic.backup-download-path: /api/v3/internal/dev/diagnostics/snapshot/8e2f1a4b.dat。 - 直接访问该备份文件路径,下载文件并在其中搜索
ISCC获得 Flag。
题目四:数字古墓
漏洞类型
- PHP 反序列化字符串逃逸 (Serialization String Escape)
- PHP 反序列化调用链构造 (POP Chain)
第一关:rune_trial.php
漏洞原理:字符串逃逸
- 代码逻辑:
- 接收参数
d和p。 - 检查
d中是否包含字符串'amgoinvc'。 - 实例化
nameA类,序列化对象。 - 调用
p1->p2函数,将序列化字符串中的'amgoinvc'替换为'iscc'。 - 对替换后的字符串进行反序列化。
- 接收参数
- 关键点:
'amgoinvc'长度为 8,'iscc'长度为 4。替换后,序列化字符串的整体长度变短,但原序列化字符串中表示字符串长度的值(如s:8:"...")并未改变,导致反序列化时解析边界错位。攻击者可以精心构造d的值,使得被“吞掉”的部分来自我们可控的p参数,从而注入新的对象属性。
利用步骤
- 目标:使反序列化后的
nameA对象的y属性值为'admin123',以触发__wakeup()包含relic_manifest.php文件。 - 构造Payload:
- 正常的序列化字符串结构为:
O:5:"nameA":2:{s:1:"x";s:L1:"CONTENT_OF_d";s:1:"y";s:L2:"CONTENT_OF_p";} - 我们需要让
CONTENT_OF_d中包含多个amgoinvc,使得替换后,y属性及其值admin123能被“吞”到前一个字符串x的声明范围内,而}之后的内容被当作新的属性定义。 - 计算:每个
amgoinvc被替换为iscc后,会空出 4 个字符的位置。我们需要逃逸出的字符串是:";s:1:"y";s:8:"admin123";},长度为 22。 - 需要逃逸的字符数 = 22。每个
amgoinvc可逃逸 4 个字符,因此需要22 / 4 = 5.5,向上取整为 6 个amgoinvc。 - 构造
d=amgoinvcamgoinvcamgoinvcamgoinvcamgoinvcamgoinvc(6个)。 - 构造
p的值为:";s:1:"y";s:8:"admin123";}(注意闭合引号和分号)。
- 正常的序列化字符串结构为:
- 结果:反序列化后,对象
y属性被成功赋值为admin123,触发__wakeup(),输出包含 Flag 文件名的提示。
第二关:mechanism_chamber.php
漏洞原理:POP链构造
- 题目给出了多个类的源码,并限制了反序列化时可用的类 (
allowed_classes)。 - 目标:通过构造一个反序列化链,最终触发
RitualEngine类的run()方法,读取第一关获得的文件名(如W3f82KD9.txt)。
调用链 (POP Chain) 分析
攻击链需要精心构造对象间的属性引用,以触发一系列魔术方法:
- 起点:
GateSentinel对象 ($outer) 被反序列化,触发其__wakeup()。 __wakeup()触发__toString():__wakeup()中对$this->object执行preg_match。如果$this->object是另一个GateSentinel对象 ($inner),PHP 会尝试将其转换为字符串,从而触发$inner的__toString()方法。__toString()触发属性访问:在$inner->__toString()中,代码访问$this->tool['blade']->object。我们让$inner->tool['blade']指向一个Keystone对象 ($key)。__get()触发:Keystone对象没有object属性,因此访问$key->object会触发Keystone的__get()方法。__get()调用回调:在Keystone::__get()中,检查$this->center。我们将$this->center设置为一个RitualEngine对象 ($invoker),并且该对象的callback属性被设置为一个序列化字符串。__get()方法判断$invoker是对象且is_callable($invoker)为真(因为RitualEngine实现了__invoke()方法),于是执行$invoker()。__invoke()触发二次反序列化:RitualEngine::__invoke()方法中,会反序列化其$callback属性。我们将$callback设置为序列化后的数组[$reader, 'view'],其中$reader是另一个RitualEngine对象,其$target属性已设置为目标文件名W3f82KD9.txt。- 最终执行:二次反序列化出数组后,代码检查
$obj是RitualEngine实例,并将'view'映射为'run',最终调用$reader->run()方法,读取$target指定的文件并输出其内容(即 Flag)。
绕过过滤
- 外层
GateSentinel::__wakeup()虽然用preg_match检查$this->object是否包含..、flag等敏感词,但我们将$this->object设置为一个对象 ($inner)。preg_match在处理对象时会触发其__toString(),从而进入我们的攻击链,而不会直接进行字符串匹配和替换。真正的文件名存储在链最深处$reader->target,不受此过滤影响。
Payload 构造
通过编写 PHP 脚本,按照上述链式关系依次创建和设置各个对象的属性,最后序列化最外层的 $outer 对象,将其作为 POST 数据提交即可。
题目五:企业公文套红预览系统
漏洞类型
- 服务端模板注入 (SSTI - Server-Side Template Injection)
- 源码泄露 (Source Code Disclosure)
解题步骤与原理分析
- 源码泄露:通过常见的路径猜测(如
/backup、.bak后缀),发现并下载了app.py.bak备份文件。 - 代码审计:分析备份源码,发现存在模板渲染功能,并且将包含
flag键值对的doc字典传递给了模板引擎(如 Jinja2)。 - SSTI 利用:
- 在预览或编辑功能处,找到用户输入被嵌入模板的位置。
- 构造 Payload 直接引用模板上下文中的变量。由于已知
doc字典包含flag,最简单的 Payload 为{{ doc }}或{{ doc['flag'] }}。 - 提交后,服务器渲染模板时会将
{{ ... }}中的表达式执行,并将结果输出到页面上,从而直接暴露 Flag 值。
- 漏洞根源:未对用户输入进行过滤或沙箱处理,直接将用户可控内容拼接进模板字符串中进行渲染。
总结与防护建议
| 漏洞类型 | 关键漏洞点 | 防护建议 |
|---|---|---|
| 身份伪造 | 仅依赖客户端 Cookie 值进行权限判断。 | 使用无状态、签名(如 JWT)或有状态的服务端会话机制,并对关键操作进行复核认证。 |
| 任意文件读取 | 文件路径参数未过滤,可直接穿越目录。 | 使用白名单机制限制可访问的文件;对输入进行规范化并检查是否在允许的基路径下;避免将用户输入直接拼接至文件路径。 |
| PHP 魔术哈希 | 使用 == 进行哈希值的松散比较。 |
密码、令牌比较应使用 hash_equals() 函数或 === 严格比较。 |
| SSRF | 服务端可对外发起任意 HTTP 请求。 | 对请求的目标 URL 进行严格过滤(白名单或有效的内网 IP 段校验);禁用不必要的 URL 协议(如 file://、gopher://)。 |
| 不安全的反序列化 | 反序列化用户可控数据,并包含危险魔术方法。 | 避免反序列化用户数据;如必须,使用严格的白名单控制可反序列化的类;或使用安全的序列化格式(如 JSON)。 |
| 字符串逃逸 | 序列化字符串在反序列化前被替换,导致长度错位。 | 避免在序列化数据上执行字符串替换操作;如必须,应在序列化之前进行替换。 |
| POP链攻击 | 类中存在危险的魔术方法(如 __wakeup, __toString, __get, __invoke)。 |
严格控制反序列化的类;在魔术方法中避免执行危险操作或调用用户可控数据。 |
| 路径解析漏洞 | 路径处理逻辑存在缺陷(如 resolve() 对绝对路径的处理、反斜杠替换)。 |
使用安全的 API 进行路径拼接和规范化(如 Path.normalize() 后与规范化的基路径进行比较);使用白名单校验最终路径。 |
| SSTI | 用户输入被直接拼接进模板字符串。 | 严格将模板代码与数据分离;使用模板引擎提供的自动转义功能;对用户输入进行严格的输入验证和过滤。 |
| 信息泄露 | 错误信息中包含敏感数据(如序列化对象、内部路径)。 | 在生产环境中禁用详细的错误提示;记录错误日志到内部系统,而非返回给客户端。 |
注:本文档内容完全基于提供的链接内容进行整理、归纳和解释。对于“Oracle's Whisper”等题目,由于链接内容未提供完整的解题过程,故未包含在本次教学文档中。
相似文章
相似文章