ISCC2026部分web题wp
字数 8186
更新时间 2026-05-22 12:33:13

ISCC 2026 部分 Web 题漏洞利用与知识点教学文档

前言

本教学文档基于 ISCC 2026 CTF 竞赛中的部分 Web 题目(Write-Up)整理而成,旨在深入解析其中涉及的网络安全漏洞原理、利用手法及防护思路。文档内容涵盖 PHP 魔术哈希、SSRF、反序列化字符逃逸、Java 路径解析缺陷、SSTI 等多个经典漏洞类型。


题目一:值班邮件台

漏洞类型

  1. Cookie 身份伪造
  2. 任意文件读取 (Arbitrary File Read)
  3. PHP 类型混淆魔术哈希漏洞 (Magic Hash)
  4. 服务端请求伪造 (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),获得了关键信息:
    1. 后台预览器仅用于查看内部诊断结果。
    2. 双人复核逻辑在 admin.php
    3. 诊断地址命名规则在 route-index.txt
  • 漏洞利用:尝试路径穿越,成功读取 route-index.txtadmin.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。同时,!== 确保了两个输入字符串本身不同。
  • 常用 Payloadtoken_a=240610708token_b=QNKCDZO
    • md5('240610708') = 0e462097431906509019562988736854
    • md5('QNKCDZO') = 0e830400451993494058024219903391

4. SSRF 获取 Flag

  • 通过魔术哈希绕过复核后,可向 target_url 参数发起请求。
  • route-index.txt 得知内部路由 final -> /internal/report?view=flag&slot=last
  • 构造 SSRF Payloadtarget_url=http://127.0.0.1/internal/report?view=flag&slot=last
  • 注意点:直接在表单中提交该 URL,避免使用某些工具(如 Yakit)将 &slot=last 错误识别为新的参数。此漏洞允许攻击者以服务器身份访问内部网络服务,从而读取本应无法外泄的内部信息(Flag)。

题目二:灵感笔记

漏洞类型

  1. 不安全的反序列化 (Insecure Deserialization)
  2. 信息泄露 (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/detail API 时返回的 trace_id
  • 漏洞利用:反馈接口返回了详细的错误信息,其中包含了一个经过编码的 stack_trace 字段。该字段是 Python pickle 序列化后的对象经过十六进制表示的字符串。
  • 获取 Flag:将 stack_trace 的十六进制字符串解码后,得到 pickle 序列化数据。反序列化该数据或直接分析其结构,可提取出包含 Flag 的 Python 对象。在本例中,解码后得到类似 b'\x80\x04\x95...FLAG_OBJECT...ISCC{...}' 的数据,从中即可找到 Flag。
  • 根本原因:系统在错误处理时,将包含敏感信息的内部对象(包括 Flag)的序列化数据直接返回给客户端,造成了严重的信息泄露。

题目三:逆向穿越

漏洞类型

  1. Java 路径遍历 (Path Traversal) 与 路径解析逻辑漏洞

前置知识:Java Path.resolve()

  • Path.resolve() 用于拼接路径。关键规则:如果传入的参数是绝对路径,则会直接返回该绝对路径,而忽略之前的路径前缀。
  • 示例:
    • Paths.get("/app/resources/config").resolve("infra") -> /app/resources/config/infra
    • Paths.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 ...; }
}

漏洞利用链:

  1. 目标:读取 /app/application.yml
  2. 构造参数
    • app: application.yml
    • profile: %5c (URL 编码的反斜杠 \)
    • filename: app/application.yml
  3. 处理流程
    • 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
  4. 通过校验:最终路径 /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。

题目四:数字古墓

漏洞类型

  1. PHP 反序列化字符串逃逸 (Serialization String Escape)
  2. PHP 反序列化调用链构造 (POP Chain)

第一关:rune_trial.php

漏洞原理:字符串逃逸

  • 代码逻辑:
    1. 接收参数 dp
    2. 检查 d 中是否包含字符串 'amgoinvc'
    3. 实例化 nameA 类,序列化对象。
    4. 调用 p1 -> p2 函数,将序列化字符串中的 'amgoinvc' 替换为 'iscc'
    5. 对替换后的字符串进行反序列化。
  • 关键点'amgoinvc' 长度为 8,'iscc' 长度为 4。替换后,序列化字符串的整体长度变短,但原序列化字符串中表示字符串长度的值(如 s:8:"...")并未改变,导致反序列化时解析边界错位。攻击者可以精心构造 d 的值,使得被“吞掉”的部分来自我们可控的 p 参数,从而注入新的对象属性。

利用步骤

  1. 目标:使反序列化后的 nameA 对象的 y 属性值为 'admin123',以触发 __wakeup() 包含 relic_manifest.php 文件。
  2. 构造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,向上取整为 6amgoinvc
    • 构造 d=amgoinvcamgoinvcamgoinvcamgoinvcamgoinvcamgoinvc (6个)。
    • 构造 p 的值为:";s:1:"y";s:8:"admin123";} (注意闭合引号和分号)。
  3. 结果:反序列化后,对象 y 属性被成功赋值为 admin123,触发 __wakeup(),输出包含 Flag 文件名的提示。

第二关:mechanism_chamber.php

漏洞原理:POP链构造

  • 题目给出了多个类的源码,并限制了反序列化时可用的类 (allowed_classes)。
  • 目标:通过构造一个反序列化链,最终触发 RitualEngine 类的 run() 方法,读取第一关获得的文件名(如 W3f82KD9.txt)。

调用链 (POP Chain) 分析

攻击链需要精心构造对象间的属性引用,以触发一系列魔术方法:

  1. 起点GateSentinel 对象 ($outer) 被反序列化,触发其 __wakeup()
  2. __wakeup() 触发 __toString()__wakeup() 中对 $this->object 执行 preg_match。如果 $this->object 是另一个 GateSentinel 对象 ($inner),PHP 会尝试将其转换为字符串,从而触发 $inner__toString() 方法。
  3. __toString() 触发属性访问:在 $inner->__toString() 中,代码访问 $this->tool['blade']->object。我们让 $inner->tool['blade'] 指向一个 Keystone 对象 ($key)。
  4. __get() 触发Keystone 对象没有 object 属性,因此访问 $key->object 会触发 Keystone__get() 方法。
  5. __get() 调用回调:在 Keystone::__get() 中,检查 $this->center。我们将 $this->center 设置为一个 RitualEngine 对象 ($invoker),并且该对象的 callback 属性被设置为一个序列化字符串。__get() 方法判断 $invoker 是对象且 is_callable($invoker) 为真(因为 RitualEngine 实现了 __invoke() 方法),于是执行 $invoker()
  6. __invoke() 触发二次反序列化RitualEngine::__invoke() 方法中,会反序列化其 $callback 属性。我们将 $callback 设置为序列化后的数组 [$reader, 'view'],其中 $reader 是另一个 RitualEngine 对象,其 $target 属性已设置为目标文件名 W3f82KD9.txt
  7. 最终执行:二次反序列化出数组后,代码检查 $objRitualEngine 实例,并将 '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 数据提交即可。


题目五:企业公文套红预览系统

漏洞类型

  1. 服务端模板注入 (SSTI - Server-Side Template Injection)
  2. 源码泄露 (Source Code Disclosure)

解题步骤与原理分析

  1. 源码泄露:通过常见的路径猜测(如 /backup.bak 后缀),发现并下载了 app.py.bak 备份文件。
  2. 代码审计:分析备份源码,发现存在模板渲染功能,并且将包含 flag 键值对的 doc 字典传递给了模板引擎(如 Jinja2)。
  3. SSTI 利用
    • 在预览或编辑功能处,找到用户输入被嵌入模板的位置。
    • 构造 Payload 直接引用模板上下文中的变量。由于已知 doc 字典包含 flag,最简单的 Payload 为 {{ doc }}{{ doc['flag'] }}
    • 提交后,服务器渲染模板时会将 {{ ... }} 中的表达式执行,并将结果输出到页面上,从而直接暴露 Flag 值。
  4. 漏洞根源:未对用户输入进行过滤或沙箱处理,直接将用户可控内容拼接进模板字符串中进行渲染。

总结与防护建议

漏洞类型 关键漏洞点 防护建议
身份伪造 仅依赖客户端 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”等题目,由于链接内容未提供完整的解题过程,故未包含在本次教学文档中。

相似文章
相似文章
 全屏