从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全

前言

今天碰到一道需要SSTI注入的题,百度了一下,发现大多数关于SSTI的基础文章上下文的连贯性并不是很好,于是便有了此文。

在阅读此文前,我假设您已具有Python和Html基础.

什么是SSTI(服务器模板注入)

SSTI 即服务端模板注入攻击,服务端接受用户输入,将其作为 Web 应用模板的一部分渲染编译后执行了恶意内容,导致敏感信息泄露,代码执行等.(与Xss原理大致相似)

什么是Flask

Flask是一个用Python编写的Web应用程序框架.它由Armin Ronacher 开发,他领导一个名为Pocco的国际Python爱好者团队.Flask基于Werkzeug WSGI工具包和Jinja2模板引擎.两者都是Pocco项目.

一个Flask应用是如何跑起来的

运行虚拟环境:

Flask是运行在虚拟环境(VirtualEnv)中的,VirtualEnv是一个虚拟的Python环境构建器。它可以帮助用户并行创建多个Python环境.因此,它可以避免不同版本的库之间的兼容性问题.(最好不要使用python3.8运行,以减少不兼容的可能性)

pip install virtualenv
virtualenv venv
cd ./venv/Scripts
activate.bat

如何跑起来:

先来看一个简单的小demo

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
   return 'Hello World'

if __name__ == '__main__':
   app.run()

首先是导入flask框架,这是必须的.

Flask构造函数使用当前模块(__name __)的名称作为参数.

Flask类的route()函数是一个装饰器,它告诉应用程序哪个URL应该调用相关的函数.

app.route(rule, options)

rule参数代表该路径与哪个函数进行绑定.如上图中的小demo,@app.route(‘/’)是函数hello_world的装饰器,即代表网站的根路径与函数hello_world绑定,在访问http://127.0.0.1/的时候,就会触发函数hello_world.

最后,Flask类的run()方法在本地开发服务器的虚拟环境上运行应用程序

图片[1]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科

图片[2]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科

Flask模板

Flask模板的基本使用

在项目下创建 templates 文件夹,用于存放所有模板文件,并在目录下创建一个模板文件 html 文件 hello.html

图片[3]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科

在env这个虚拟环境中,render_template()这个函数会自动寻找在同级目录下templates文件夹下的文件.

也就是说render_template(‘hello.html’)等价于render_template(‘./templates/hello.html’)

模板变量(重点)

传入字符串到html模板中

图片[4]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科图片[5]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科在html模板中使用{{变量名}}的方法输出变量内容

图片[6]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科在html模板中,{{}}之间的内容会被当做表达式进行执行,并将结果输出到页面上

一个存在SSTI漏洞的demo

在ctf中基于python开发的web,存在SSTI漏洞的一般都使用的是Jianja2模板引擎

什么是Jianja2引擎

Jinja2是Python下一个被广泛应用的模版引擎,他的设计思想来源于Django的模板引擎,并扩展了其语法和一系列强大的功能。.中最显著的一个是增加了沙箱执行功能和可选的自动转义功能.

注意这一句话,可选的自动转义功能,即默认不进行转义.这为我们进行SSTI注入创造了条件.

test.py

from flask import Flask, request
from jinja2 import Template
app = Flask(__name__)

@app.route('/')
def index():
    id = request.args.get('id')
    t = Template('''
    <html>
      <head>
        <title></title>
      </head>
     <body>
          <h1>%s</h1>
      </body>
    </html>
        ''' % (id))
    return t.render()
app.run()

图片[7]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科

表达式果然被执行了.

一个不存在SSTI漏洞的demo

test.py

from flask import Flask, request,render_template
app = Flask(__name__)

@app.route('/')
def index():
    id = request.args.get('id')
    return render_template('hello.html',id=id)
app.run()

使用request对象,处理来自客户端url中id的数据.

request.args.get() 解析查询字符串的内容,它是问号(?)之后的URL的一部分。

html模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{{id}}
</body>
</html>

图片[8]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科flask中的render_plate函数会对变量内容进行转义,即输出原始字符串.所以不存在SSTI漏洞.

如何对存有SSTI漏洞的flask进行命令执行

这里一道简单的ctf练习题为例.你可能会疑惑,没有导入OS模块,也没有system()类似的函数怎么能进行命令执行呢?

命令执行的基本流程:获取基本类->获取基本类的子类->在子类中找到关于命令执行和文件读写的模块

获取基本类

我们都知道在python中一切皆为对象.

使用__bases__获取基本类

Python 为所有类都提供了一个 bases属性,通过该属性可以查看该类的所有直接父类,该属性返回所有直接父类组成的元组,注意是直接父类.

''.__class__.__bases__

图片[9]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科

可以看到str的父类是basestring

使用__mro__同样也可以查找父类,不同的是__mro__获取的是类的调用顺序

图片[10]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科调用顺序依次是object->basestring->str

获取基本类的子类

”.__class__.__mro__[2].__subclasses__()

图片[11]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科在子类中找到关于命令执行和文件读写的模块

在上图中我们要找到’os’ 所在的’site._Printer’类,它的索引为[71]

使用__subclasses__()[71].__init__.__globals__[‘os’].popen(‘cmd’).read(),运行命令并得到回显结果

图片[12]-从0到1:Flask SSTI(服务器模板注入) – 作者:菜头想学安全-安全小百科

总结

SSTI模板注入的漏洞好像并不太常见,以后遇到再更新吧。

来源:freebuf.com 2021-07-06 13:40:00 by: 菜头想学安全

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论