1. 项目概述:从SSTI漏洞到WAF对抗的实战演练
在Web安全测试的日常工作中,服务器端模板注入(SSTI)漏洞因其强大的利用潜力,一直是渗透测试人员和红队成员关注的重点。它不像SQL注入那样广为人知,但一旦成功利用,往往能直接获取服务器权限,危害极大。然而,随着企业安全意识的提升,Web应用防火墙(WAF)的部署越来越普遍,传统的SSTI利用Payload往往在发出请求的瞬间就被拦截,测试工作常常陷入僵局。这时,一个名为fenjing的工具进入了我的视野。它不仅仅是一个漏洞检测工具,更是一个集成了自动化Payload生成、WAF绕过和漏洞利用的“瑞士军刀”。今天,我就结合自己多次在内外网渗透测试中的实战经历,来详细拆解如何使用fenjing高效、隐蔽地完成从SSTI漏洞发现到绕过WAF成功利用的全过程。无论你是刚开始接触Web安全的爱好者,还是苦于WAF拦截的资深安全工程师,这篇指南都将提供一套清晰、可复现的实战思路。
简单来说,fenjing的核心价值在于“自动化”和“智能化”。它内置了对多种常见模板引擎(如Jinja2、Twig、Smarty等)的语法解析和Payload生成逻辑,并能通过多种编码、混淆、分割等技术,尝试绕过WAF的规则匹配。你不需要再手动记忆那些冗长且易被拦截的Payload,也不需要花费大量时间手工Fuzz绕过姿势。fenjing试图将安全研究员在对抗WAF过程中积累的经验,转化为可以自动执行的算法。接下来,我将从环境准备、核心原理、实战操作到深度对抗,一步步带你掌握这门“艺术”。
2. 核心原理与工具设计思路拆解
2.1 SSTI漏洞的本质与利用链构造
要理解fenjing的强大,首先得明白SSTI漏洞为什么危险,以及手工利用的难点在哪里。模板引擎是为了将动态数据(变量)嵌入到静态页面(模板)中而设计的,比如Jinja2中的{{ 7*7 }}会被渲染为49。漏洞的产生,在于应用将用户输入未经充分过滤就直接拼接进了模板语句中进行渲染。例如,一个接收name参数并渲染Hello {{name}}!的应用,如果用户传入name={{config}},就可能泄露Flask的配置信息。
手工利用SSTI,通常遵循一个“步步为营”的链条:1. 检测漏洞并识别模板引擎类型->2. 寻找并调用危险的内置函数或对象->3. 利用这些函数构造文件读写或命令执行的Payload。例如,在Jinja2中,我们可能会利用__class__、__mro__、__subclasses__()这条链来回溯到object基类,再从其子类中找到os模块或subprocess.Popen类,最终实现命令执行。这个过程不仅需要深厚的知识储备,而且构造出的Payload往往很长,特征明显,极易被WAF识别。
2.2 WAF的防御机制与fenjing的绕过哲学
主流的WAF(如Cloudflare、ModSecurity、阿里云盾等)通常基于规则(正则表达式匹配)和语义分析来拦截攻击。它们会维护一个庞大的恶意特征库,一旦检测到请求中含有os.system、eval、__import__等关键词,或者{{、}}等模板语法与危险函数组合出现时,就会触发拦截。
fenjing的绕过思路,正是针对这些防御机制的“七寸”进行设计:
- 关键词分割与混淆:将
os和system拆分成两个变量或通过字符串拼接生成,避免直接匹配。 - 多种编码方式:对Payload进行URL编码、HTML实体编码、十六进制编码、Unicode编码等,干扰WAF的解析。
- 利用模板引擎特性:不同引擎有特殊的语法可以替代常见写法。例如,Jinja2中可以使用
|attr()过滤器来替代点号.调用属性,使用request.args.x来替代request.args[‘x’]以绕过对中括号的检测。 - Payload变异与Fuzz:
fenjing内置了大量绕过“模板”,通过排列组合,自动生成成千上万种变体进行测试,从中找出能绕过当前WAF规则的那一个。
它的设计哲学是:将手工测试中“灵光一现”的绕过技巧,转化为系统性的、可穷举的测试流程。工具负责生成和尝试所有可能性,测试者则专注于目标分析和结果判断。
2.3fenjing的工作流程与架构
理解其工作流程,能帮助我们在使用时更好地定位问题。fenjing通常以命令行交互模式运行,其内部处理流程可以简化为以下几步:
- 信息收集与引擎识别:用户提供目标URL和测试参数。工具可能会先发送一些无害的探测Payload(如
{{7*‘7’}}),根据返回结果(是49还是7777777)来判断模板引擎类型和漏洞是否存在。 - Payload生成器启动:根据识别的引擎类型,调用对应的Payload生成模块。该模块基于一个“语法树”和“绕过规则库”来构造Payload。
- WAF绕过层介入:生成的原始Payload会送入“绕过处理器”。这里会应用前述的各种混淆、编码、分割技术,产生一批候选Payload。
- 智能发包与检测:工具将候选Payload依次发送给目标,并分析HTTP响应。它不仅仅看返回状态码(因为WAF可能返回200但内容被阻断),更会智能分析响应内容中是否包含了Payload执行的“证据”,比如计算结果的回显、特定错误信息、或者通过盲注技术判断的布尔值。
- 结果报告与利用:将成功的Payload报告给用户,并可能进一步提供交互式Shell或文件管理功能。
3. 环境准备与工具部署详解
3.1 本地测试环境搭建
在直接对真实目标进行测试前,强烈建议在本地搭建一个包含漏洞和WAF的模拟环境。这既能让你熟悉工具流程,又不会产生法律风险。这里我提供一个基于Docker的快速搭建方案。
首先,我们创建一个存在SSTI漏洞的Flask应用。新建一个app.py文件:
from flask import Flask, request, render_template_string import os app = Flask(__name__) @app.route('/') def index(): name = request.args.get('name', 'Guest') # 这里存在SSTI漏洞,用户输入被直接渲染 template = f"<h1>Hello {name}!</h1>" return render_template_string(template) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)这个应用非常简单,从URL参数name获取值,并直接拼接到模板字符串中渲染,构成了一个典型的Jinja2 SSTI漏洞。
接下来,我们使用docker-compose来部署一个带有ModSecurity WAF的Nginx作为反向代理,保护我们的漏洞应用。创建docker-compose.yml:
version: '3' services: vuln-app: build: . ports: - "5000:5000" nginx-waf: image: owasp/modsecurity-crs:nginx ports: - "80:80" volumes: - ./modsecurity.conf:/etc/modsecurity.d/modsecurity.conf - ./crs-setup.conf:/etc/modsecurity.d/crs-setup.conf depends_on: - vuln-app同时,需要准备一个简单的Dockerfile来构建漏洞应用,以及ModSecurity的配置文件modsecurity.conf和crs-setup.conf。在modsecurity.conf中,确保开启了检测引擎(SecRuleEngine On)并加载OWASP CRS核心规则集。这样,一个受WAF保护的SSTI漏洞靶场就准备好了。运行docker-compose up即可启动。
注意:在真实学习中,你可以从Vulhub、DVWA等知名漏洞靶场项目中找到更标准的SSTI漏洞环境,它们通常包含了多种模板引擎的漏洞场景。
3.2fenjing的安装与基础配置
fenjing是一个Python工具,推荐在Python 3.7+环境中使用。安装非常简单:
pip install fenjing对于追求最新功能的用户,也可以从GitHub克隆源码安装:
git clone https://github.com/ProjectAnte/fenjing.git cd fenjing pip install -e .安装完成后,在命令行输入fenjing --help,可以看到完整的参数列表。核心参数包括:
-u/--url: 指定目标URL。-p/--param: 指定存在漏洞的参数名。--method: 请求方法(GET/POST)。--headers: 自定义请求头,用于绕过一些基础认证或添加UA伪装。--cookies: 维持会话的Cookies。--proxy: 设置代理,方便使用Burp Suite等工具拦截观察流量。--full: 启用完整扫描模式,尝试获取Shell。
一个最基础的检测命令形如:fenjing -u “http://target.com/?name=test" -p name。但在实战前,有几点配置心得:
配置心得一:代理设置是必备步骤。务必通过--proxy http://127.0.0.1:8080将流量导向Burp Suite。这样你可以清晰地看到fenjing发送的每一个Payload的形态,观察WAF的拦截和绕过情况,这对于理解工具行为和后续手动微调至关重要。
配置心得二:善用自定义Header。有些WAF或应用会检查User-Agent,甚至X-Forwarded-For等头。使用--headers “User-Agent: Mozilla/5.0…“可以将其伪装成普通浏览器流量。在某些情况下,添加一个看似随机的X-Client-IP头可能有助于绕过简单的IP黑名单规则。
4. 实战演练:分步击破SSTI与WAF
4.1 第一阶段:漏洞探测与引擎指纹识别
实战开始,假设我们的目标是http://test.local/vuln?input=123。我们不确定是否存在漏洞,也不清楚后端模板引擎。
首先,使用fenjing进行初步探测:
fenjing -u “http://test.local/vuln?input=123" -p input --proxy http://127.0.0.1:8080工具会自动发送一批无害的探测Payload,例如:
{{7*7}}-> 如果返回49,可能是Jinja2/Twig。${7*7}-> 如果返回49,可能是Elasticsearch / Thymeleaf。<%= 7*7 %>-> 如果返回49,可能是ERB(Ruby)。${{7*7}}-> 如果返回49,可能是AngJS。
在Burp Suite中,你会看到一系列这样的请求。观察响应,如果某个Payload的计算结果被回显(比如页面中出现了49),那么基本可以确定SSTI漏洞存在,并且工具也能大概率识别出引擎类型。
踩坑记录:有时应用会对输入进行部分过滤或编码,导致回显不明显。例如,结果可能出现在HTML注释、JavaScript代码段或者被
urlencode后输出。此时需要仔细查看响应源码,而不仅仅是渲染后的页面。fenjing的智能检测会分析响应文本,但人工复核永远是必要的。
4.2 第二阶段:绕过WAF获取初始信息
确认漏洞后,fenjing会进入更积极的攻击模式。但此时,直接使用{{config}}这样的经典Payload很可能触发WAF,返回403或一个通用的拦截页面。
fenjing的优势在这里体现。它会开始尝试各种绕过技术。例如,对于{{config}}:
- 字符串拼接:
{{“co”+“nfig”}}或{{“c”“o”“n”“f”“i”“g”}} - 属性访问替代:
{{config|attr(“__class__”)}}(虽然这里不适用,但展示了|attr的用法) - 利用请求对象:在某些框架中,可以通过
{{request.application.__self__._get_data_for_json.__globals__[‘config’]}}这类更迂回的链式调用。fenjing的生成器会基于引擎类型,尝试构造大量此类变体。
这个阶段的目标可能不是直接执行命令,而是先读取一些关键配置信息,为后续利用铺路。例如,成功获取到{{config}}后,你可能从中发现SECRET_KEY、数据库连接信息、服务器路径等。
实操技巧:如果工具跑了一段时间仍未成功,可以暂停,观察Burp中已被拦截的Payload特征。例如,你发现所有包含__class__的请求都被拦了,但包含|attr的没有。那么可以尝试给fenjing命令增加一个--test参数(如果支持),或者手动构造几个Payload验证猜想,再将有效的绕过思路反馈给工具的后续测试(有时需要手动调整源码或等待新版本)。
4.3 第三阶段:构造命令执行与交互式Shell
在能够成功执行简单表达式后,下一步就是突破沙箱或限制,实现命令执行。fenjing会自动尝试构造从模板上下文到危险函数的调用链。
以Jinja2为例,手工链可能长这样:
{{””.__class__.__base__.__subclasses__()[X].__init__.__globals__[‘os’].popen(‘id’).read()}}你需要找到os模块所在的子类索引X,这通常需要遍历。fenjing会自动完成这个遍历过程。
在WAF面前,这个长链会被拆解得“支离破碎”。fenjing可能生成如下形式的Payload:
{% set a = “__cla” %}{% set b = “ss__” %}{{””|attr(a+b)|attr(“__base__”)|attr(“__subclasses__”)()|attr(“__getitem__”)(132)|attr(“__init__”)|attr(“__globals__”)|attr(“get”)(“os”)|attr(“popen”)(“whoami”)|attr(“read”)()}}它通过{% set %}定义变量进行分割,使用|attr过滤器进行属性调用,用__getitem__替代中括号索引。这种写法极大地增加了WAF规则匹配的难度。
当命令执行成功后,fenjing可能会返回命令执行的结果。更高级的用法是使用--full参数,尝试自动上传一个Web Shell或建立反向Shell连接。
深度对抗心得:在一些防护极其严格的环境中,即使如此混淆的Payload也可能被基于语义分析或机器学习的WAF拦截。此时需要结合更多上下文:
- 参数污染:将Payload拆分到多个参数中。例如,
?arg1={{””.__class__}}&arg2={{__base__}},然后在模板中组合。这需要了解应用如何拼接多个输入。 - 请求方法混合:将部分数据放在POST Body,部分放在URL参数或Cookie中。
- 时间延迟盲注:如果没有任何回显,
fenjing可能会尝试使用基于时间延迟的盲注Payload,如{{””.__class__.__base__.__subclasses__()[X].__init__.__globals__[‘time’].sleep(5) if condition else ”}},通过响应时间来判断是否执行成功。这虽然慢,但极其隐蔽。 - 利用WAF规则盲区:有些WAF默认只检查前N个字节或某些特定参数。尝试将Payload放在不常见的HTTP头(如
X-Forwarded-Host)或JSON/XML格式的POST数据中,有时能奇效。
5. 高级技巧与深度对抗策略
5.1 手动微调Payload以应对定制化WAF
完全依赖自动化工具有时会失灵,特别是面对企业自研或深度定制的WAF规则时。这时就需要我们根据拦截情况,手动干预Payload的构造。
案例:在一次测试中,我发现目标WAF对request对象访问args属性进行了严格拦截。无论怎么分割request.args.x,都会被拦。但是,我注意到应用使用了Flask框架。Flask中,除了request.args,还可以通过request.values来同时获取GET和POST参数,而request.values这个特征可能不在WAF规则库里。于是,我手动构造了Payload:{{request.values.get(‘cmd’)}},并通过另一个参数?cmd=whoami传递命令,成功绕过了防御。
策略总结:
- 信息收集是关键:尽可能多地了解目标应用的技术栈(框架、中间件、模板引擎版本)。
fenjing的探测结果是一个起点,通过查看HTTP响应头、错误信息、静态资源等,可以获取更多线索。 - 研究模板引擎的“偏门”特性:每个模板引擎都有一些不常用但功能强大的特性。比如Jinja2的
namespace对象、内置的range、cycler等函数,有时能用来构造无数字符串或绕过过滤。 - 上下文感知:观察漏洞点所在的模板上下文。是否导入了某些自定义模块?是否有可用的全局函数或对象?直接利用这些已有的“跳板”,往往比从零开始构造链更短,也更不易被检测。
5.2 结合其他工具进行协同测试
fenjing并非孤军奋战。将它与其他工具结合,能形成更强大的测试矩阵。
- 与Burp Suite的Intruder结合:当
fenjing识别出某个基础Payload有效但被WAF拦截时,可以将这个Payload复制到Burp Intruder中,使用Cluster bomb攻击类型,对Payload中的多个位置(如函数名、字符串分割方式)进行同步Fuzz,效率可能比工具的单线程序列测试更高。 - 使用
tplmap进行交叉验证:tplmap是另一款经典的SSTI自动化利用工具。有时fenjing绕不过的WAF,用tplmap的某个特定Payload可能成功,反之亦然。两者可以互为补充。 - 自定义字典与脚本:将
fenjing生成的被拦截的Payload收集起来,分析其共同特征。然后编写简单的Python脚本,针对这些特征进行定向变异(如随机插入空白字符、换行符、特定注释符等),生成一个自定义的Fuzz字典,再交由Intruder进行测试。
5.3 防御视角:如何防范此类自动化攻击
作为一名安全测试者,了解攻击手段的最终目的是为了更好的防御。从防御角度看,应对fenjing这类自动化工具,需要多层布防:
- 输入验证与过滤(治本):严格禁止用户输入直接进入模板渲染。如果需要动态渲染,应使用安全的模板变量替换,或者使用白名单机制,只允许特定的、无害的字符或模板语句。
- 沙箱化模板执行环境:如果业务必须使用动态模板,应在一个严格的沙箱环境中执行,移除或禁用危险的内置函数和模块(如
os、subprocess、eval、exec等)。 - WAF规则精细化:通用WAF规则可能不够。需要针对业务使用的模板引擎,定制化防护规则。例如,可以监控短时间内对同一参数发送大量不同变异Payload的请求,这很可能是自动化工具在扫描。
- 输出编码:即使模板注入成功,如果执行结果在输出到页面时被正确地进行HTML编码,那么系统命令的结果可能会以纯文本形式显示,而非被执行,这能在一定程度上缓解危害(但并非根本解决,因为攻击者仍可能通过带外通道获取数据)。
- 代码审计与安全意识:在开发阶段就引入安全代码规范,定期进行代码审计,避免SSTI漏洞的产生。
6. 常见问题排查与实战心得记录
在实际使用fenjing的过程中,你肯定会遇到各种各样的问题。下面是我总结的一些常见情况及其应对方法。
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 工具运行后无任何输出,或立刻退出。 | 1. Python环境或依赖问题。 2. 目标URL无法访问。 3. 参数格式错误。 | 1. 检查Python版本(python –version),确保>=3.7。使用pip list grep fenjing确认安装成功。2. 用 curl或浏览器手动访问目标URL,确认网络连通性和服务状态。3. 仔细检查 -u参数后的URL是否用引号括好(尤其当包含&符号时),-p参数是否正确。 |
| 工具能识别漏洞,但生成的Payload全部被WAF拦截。 | 1. WAF规则非常严格,通用绕过无效。 2. 工具生成的Payload特征仍被识别。 3. 漏洞点有额外的过滤或编码。 | 1. 开启代理,逐一分析被拦截的Payload。寻找未被拦截的“漏网之鱼”,分析其特点。 2. 尝试使用 –delay参数增加请求间隔,模拟人工操作。3. 切换到 –level更高的扫描级别(如果支持),尝试更冷门的绕过方式。4. 考虑是否存在时间盲注的可能,观察是否有 timeout参数可配置。 |
| Payload执行成功,但无回显。 | 1. 命令执行结果未输出到响应中(盲注)。 2. 存在输出长度限制或截断。 3. 执行环境受限(如无回显函数)。 | 1. 尝试使用DNS外带或HTTP请求外带数据。fenjing的–full模式可能会尝试这类方式。2. 尝试执行 sleep 5等命令,通过响应时间判断是否执行成功(时间盲注)。3. 尝试写入一个文件到Web目录,再通过浏览器访问该文件查看结果。 |
| 工具误报,将正常回显识别为漏洞。 | 1. 应用本身会对输入进行数学运算或字符串变换。 2. 响应中偶然包含了与Payload相同的数字。 | 1. 使用更复杂的检测Payload,如{{7*’7’}},在Jinja2中会得到7777777,而纯数学计算或巧合的概率极低。2. 人工验证:尝试注入一个绝对唯一的字符串,如 {{“ssti_test_xyz”}},查看是否原样输出。 |
| 运行过程中被目标IP封禁。 | 工具发送请求频率过高,触发目标速率限制或入侵防御系统(IPS)。 | 1. 立即停止扫描。 2. 后续测试时,务必使用 –delay或–timeout参数,降低请求频率,如–delay 2表示每个请求间隔2秒。3. 使用代理池(如果工具支持)来切换源IP。 |
最后的心得分享:fenjing这类自动化工具极大地提升了SSTI漏洞测试的效率,但它绝不是“银弹”。它封装的是已知的、模式化的攻击手法。面对顶尖的、深度定制的防御体系,真正起决定性作用的,依然是测试者对漏洞原理、模板引擎特性、编程语言和网络协议的深入理解,以及那份在反复受挫中依然能冷静分析、另辟蹊径的耐心和创造力。工具解放了我们的双手,但思考和策略永远无法被自动化替代。每次测试结束后,花时间复盘一下工具成功或失败的Payload,理解其背后的绕过原理,这份积累才是你安全能力成长的真正阶梯。