GHCTF2025-WEB题解:如何用SSTI绕过WAF黑名单(附实战payload)

从GHCTF2025实战出发:深度拆解SSTI黑名单绕过策略与高阶Payload构造

最近在GHCTF2025的WEB赛道上,一道看似简单的文件上传题目,却让不少选手陷入了“知道有洞,但payload总被拦截”的困境。这道题表面上是文件上传,实际上却是一场针对SSTI(服务器端模板注入)绕过能力的深度考验。我在实际测试中发现,很多选手能够快速识别出SSTI漏洞的存在,但在面对严格的黑名单过滤时,却往往束手无策,反复尝试的payload都被WAF无情拦截。

这种情况在真实的渗透测试和CTF比赛中并不少见。WAF(Web应用防火墙)的过滤规则越来越智能,传统的{ {7*7}}测试虽然能确认漏洞,但真正要执行命令、读取文件时,那些包含osflag__builtins__等关键词的payload几乎都会被第一时间拦截。这道题的精妙之处在于,它模拟了一个相对真实的防御环境——不仅过滤常见敏感词,还对下划线这种在Python反射中至关重要的字符进行了拦截。

本文将从实战角度出发,不局限于GHCTF2025这一道题目,而是系统性地探讨SSTI黑名单绕过的核心思路、技术原理和进阶技巧。我会结合自己多次踩坑的经验,分享几种真正有效的绕过方法,并深入分析每种方法背后的Python/Jinja2特性。无论你是CTF新手,还是有一定经验的安全爱好者,相信都能从中获得实用的技术提升。

1. 理解题目环境与WAF过滤机制

在开始构造绕过payload之前,我们必须先彻底理解题目设置的防御环境。很多选手失败的原因不是技术不行,而是没有仔细分析过滤规则,盲目尝试各种已知的payload。

1.1 代码审计:识别真正的攻击面

题目虽然以文件上传为入口,但通过代码审计可以发现,上传功能本身被严格限制:

ALLOWED_EXTENSIONS = {'txt', 'log', 'text','md','jpg','png','gif'} def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS 

这是典型的白名单过滤,只允许特定扩展名的文件。更重要的是secure_filename()函数的使用,它会清理文件名中的特殊字符,防止路径遍历等攻击。真正的漏洞点在于文件查看功能:

tmp_str = """<!DOCTYPE html> <html lang="zh"> <head>...</head> <body> <h1>文件内容:{name}</h1> <pre>{data}</pre> </body> </html>""".format(name=safe_filename, data=file_data) return render_template_string(tmp_str) 

这里的关键是render_template_string()函数,它将用户控制的文件内容直接渲染为模板。如果文件内容包含Jinja2模板语法,就会被执行。

1.2 WAF黑名单分析

题目中的WAF实现相对简单但有效:

def contains_dangerous_keywords(file_path): dangerous_keywords = ['_', 'os', 'subclasses', '__builtins__', '__globals__', 'flag',] with open(file_path, 'rb') as f: file_content = str(f.read()) for keyword in dangerous_keywords: if keyword in file_content: return True return False 

这个过滤列表设计得相当有针对性:

过滤关键词在SSTI中的作用绕过难度
_属性访问分隔符、双下划线前缀
os执行系统命令的关键模块
subclasses获取子类链的核心方法
__builtins__内置函数和模块的入口
__globals__访问全局命名空间
flag目标文件名
注意:这里的过滤是内容检测,不是参数名检测。这意味着即使我们将payload放在URL参数中,只要文件内容包含这些关键词,就会被拦截。

1.3 初步测试与漏洞确认

在构造复杂payload之前,先用最简单的测试确认漏洞:

# 创建测试文件 echo '{ {7*7}}' > test.txt # 上传后访问 curl http://target/file/test.txt 

如果返回的页面中显示49而不是{ {7*7}},就确认了SSTI漏洞的存在。这个测试payload不包含任何黑名单关键词,所以能顺利通过WAF。

2. 基础绕过技术:编码与字符串操作

当直接使用敏感关键词被拦截时,最直接的思路就是不让这些关键词以明文形式出现。编码和字符串操作是绕过关键词过滤的经典方法。

2.1 十六进制编码绕过

Python和Jinja2都支持十六进制表示法,这为我们绕过关键词检测提供了可能。在Python字符串中,\x后跟两个十六进制数字表示一个字符。

原始payload(会被拦截):

{ { lipsum.__globals__.__builtins__.open('/flag').read() }} 

十六进制编码版本:

{ { lipsum["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["open"]("/fla\x67").read() }} 

Read more

前端通用 Token 全流程操作指南(常见常用版)

前端通用 Token 全流程操作指南(常见常用版) 本文梳理 所有前端框架通用 的 Token 操作逻辑,剥离具体项目/技术栈细节,聚焦「获取→存储→使用→过期→清除」的核心生命周期,每个步骤均标注「通用场景+通用方案+注意事项」,适合所有前端开发场景,可直接作为开发速查表。 前置说明:Token 的核心定位 Token 是后端签发的临时访问凭证,核心作用是: 1. 证明“当前用户是谁”(身份认证); 2. 证明“当前用户有权限访问”(权限校验)。 一、第一步:登录成功获取 Token 通用场景 用户通过账号密码/验证码/第三方登录等方式,向后端发起登录请求,后端验证通过后,在响应体中返回 Token。

前端图片加载失败、 img 出现裂图的原因全解析

在前端开发过程中,我们几乎都遇到过这种情况: 页面中某张图片加载不出来,显示成一个小小的“裂图”图标。 这看似简单的问题,实际上可能由多种原因造成,尤其是在 HTTPS 环境下,混合内容机制(Mixed Content) 是最常见、也最容易被误解的根源之一。 本文将带你系统梳理裂图的各种原因、排查思路,并重点讲清楚混合内容的原理与浏览器行为。 一、什么是“裂图”? “裂图”(broken image)是指浏览器尝试加载 <img> 标签的图片资源失败时的表现形式。 常见表现: * 图片区域显示为灰底、叉号、占位符; * 控制台出现 Failed to load resource 或 Mixed Content 警告; * Network 面板中图片请求状态码为 404 / 403 / blocked。 二、常见的裂图原因汇总

WebRTC / HLS / HTTP-FLV 的本质区别与选型指南

WebRTC / HLS / HTTP-FLV 的本质区别与选型指南

在做系统级直播(而不是自己本地播放)时,很多人都会遇到一个经典问题: WebRTC、HLS、HTTP-FLV 到底有什么区别? 项目中到底该选哪个? 传输协议不同 → 延迟不同 → 兼容性 / 稳定性 / 成本不同 在系统里选哪个,核心看两点: 你要多低的延迟?你要多强的兼容和稳定? 一、简介 * WebRTC:超低延迟(0.2 ~ 1s),适合实时监控、无人机、实时指挥 * HLS(hls.js):最稳、最通用(5 ~ 15s),适合活动直播、课程、公开大并发 * HTTP-FLV(flv.js):中低延迟(1 ~ 3s),适合想比 HLS 低延迟,但不想用 WebRTC 的场景(

SDWebImage 在 Flutter 中的使用:通过插件桥接

SDWebImage 在 Flutter 中的使用:通过插件桥接 关键词:SDWebImage、Flutter插件、跨平台桥接、MethodChannel、图片加载缓存 摘要:本文将带你探索如何在 Flutter 中通过插件桥接技术调用 iOS 原生的 SDWebImage 库。我们会从背景需求出发,用“跨国快递”的比喻解释桥接原理,逐步拆解核心概念,结合代码实战演示如何实现图片加载与缓存,并总结常见问题与未来优化方向。即使你是 Flutter 新手,也能轻松理解跨平台桥接的底层逻辑! 背景介绍 目的和范围 在 Flutter 开发中,图片加载是高频需求。虽然 Flutter 自带 cached_network_image 等第三方库,但在 iOS 平台上,原生的 SDWebImage 经过多年优化,在缓存策略、