SSTI 绕过 WAF 黑名单策略与高阶 Payload 构造实战
在 GHCTF2025 的 WEB 赛道上,一道看似简单的文件上传题目,却让不少选手陷入了'知道有洞,但 payload 总被拦截'的困境。这道题表面上是文件上传,实际上却是一场针对 SSTI(服务器端模板注入)绕过能力的深度考验。很多选手能够快速识别出 SSTI 漏洞的存在,但在面对严格的黑名单过滤时,却往往束手无策,反复尝试的 payload 都被 WAF 无情拦截。
这种情况在真实的渗透测试和 CTF 比赛中并不少见。WAF(Web 应用防火墙)的过滤规则越来越智能,传统的 {{7*7}} 测试虽然能确认漏洞,但真正要执行命令、读取文件时,那些包含 os、flag、__builtins__ 等关键词的 payload 几乎都会被第一时间拦截。这道题的精妙之处在于,它模拟了一个相对真实的防御环境——不仅过滤常见敏感词,还对下划线这种在 Python 反射中至关重要的字符进行了拦截。
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():
dangerous_keywords = [, , , , , ]
(file_path, ) f:
file_content = (f.read())
keyword dangerous_keywords:
keyword file_content:

