01 背景
NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。NoSQL(不仅仅是 SQL)是数据存储的一个流行趋势;它泛指依赖于不同存储机制的非关系型数据库,这些存储机制包括文档存储、键值对存储和图。
这些数据库的广泛应用是由现代大型应用推动起来的,比如 Facebook、Amazon 和 Twitter,它们需要把数据分布到许多的服务器上。
传统关系型数据库不满足这种扩展性需求,它们需要一个单独的数据库节点去执行同一事务的所有操作。在本文中,我们将分析 NoSQL 的威胁和技术
02 基础知识
Web 应用和服务通常使用 NoSQL 数据库去保存客户数据。
在MongoDB的FAQ里面有这样一段话:
"..with MongoDB we are not building queries from string , so traditional SQL injection attacks are not a problem."
意思是:使用MongoDB,我们不从字符串生成查询,因此传统的SQL注入攻击对此无效(老王自己翻译)
因此大多数的开发者以为这样就高枕无忧了。其实他们的说法并无错误。
传统的SQLi手段是不可行的。因为MongoDB所要求的输入形式是json的格式,
例如:find({'key1';'value1'})在实际的使用中(PHP环境下),一般是这样使用$collection->find(array('key'=> 'value'));对于习惯传统的SQL注入手段的我们来讲,这样的形式很难想到常规的方法去bypass也很难想到办法去构造payload,这种手段就像参数化的SQL语句一样很难注入。
想要找到真的漏洞成因和原理,了解最基础的MongoDB语法是必要的,下面做简略介绍:
条件操作符
$gt : >
$lt : <
$gte: >=
$lte: <=
$ne : !=、<>
$in : in
$nin: not in
$all: all
$or:or
$not: 反匹配(1.3.3及以上版本)
模糊查询用正则式:db.customer.find({'name': {'$regex':'.*s.*'} })
/**
* : 范围查询 { "age" : { "$gte" : 2 , "$lte" : 21}}
* : $ne { "age" : { "$ne" : 23}}
* : $lt { "age" : { "$lt" : 23}}
*/
03 NoSQL攻击途径
Web应用和服务通常使用NoSQL数据库去保存客户数据。
下图展示了一个典型的架构,在此NoSQL用于保存通过Web应用来存取的数据。通过一个驱动程序来进行这个数据库的访问,即一个存取协议包装器,它为多种编程语言编写的数据库客户端提供类库。
尽管该驱动程序自身可能不易受到攻击,但有时它们提供了不安全的API,当应用开发人员错误地使用它们时,就会给该应用引入漏洞了,这些漏洞会被人利用对数据库进行任意操作。
如图所示,攻击者可以伪造一个带有注入代码的Web访问请求,当数据库客户端或协议包装器进行处理时,将会执行预期的非法数据库操作。
图中典型Web应用架构。NoSQL用于保存通过Web应用来存取的数据。通过一个驱动程序来进行这个数据库的访问,即一个存取协议包装器,它为多种编程语言编写的数据库客户端提供类库。
尽管该驱动程序自身可能不易受到攻击,但有时它们提供了不安全的API,当应用开发人员错误地使用它们时,就会给该应用引入漏洞了。NoSQL相关的SQL攻击主要机制可以大致分为以下五类:
· 1.重言式。又称为永真式。此类攻击是在条件语句中注入代码,使生成的表达式判定结果永远为真,从而绕过认证或访问机制。例如,在本文中,我们将展示攻击者如何用$ne操作(不相等)的语法让他们无需相应的凭证即可非法进入系统。
· 2.联合查询。联合查询是一种众所周知的SQL注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。联合查询最常用的用法是绕过认证页面获取数据。在本文中,我们将展示一个攻击示例,它将通过增加永真的表达式利用布尔OR运算符进行攻击,从而导致整个语句判定出错,进行非法的数据获取。
· 3.JavaScript注入。这是一种新的漏洞,由允许执行数据内容中JavaScript的NoSQL数据库引入的。JavaScript使在数据引擎进行复杂事务和查询成为可能。传递不干净的用户输入到这些查询中可以注入任意JavaScript代码,这会导致非法的数据获取或篡改。
· 4.背负式查询。在背负式查询中,攻击者通过利用转义特定字符(比如像回车和换行之类的结束符)插入由数据库额外执行的查询,这样就可以执行任意代码了。
· 5.跨域违规。HTTP REST APIs是NoSQL数据库中的一个流行模块,然而,它们引入了一类新的漏洞,它甚至能让攻击者从其他域攻击数据库。在跨域攻击中,攻击者利用合法用户和他们的网页浏览器执行有害的操作。在本文中,我们将展示此类跨站请求伪造(CSRF)攻击形式的违规行为,在此网站信任的用户浏览器将被利用在NoSQL数据库上执行非法操作。通过把HTML格式的代码注入到有漏洞的网站或者欺骗用户进入到攻击者自己的网站上,攻击者可以在目标数据库上执行post动作,从而破坏数据库。
04 威胁
举出几个例子:
//查询age = 22的记录db.userInfo.find({"age": 22});//相当于:select * from userInfo where age = 22;//查询age > 22的记录db.userInfo.find({age: {$gt: 22}});//相当于:select * from userInfo where age > 22;
我们发现,在find的参数里,age对应的value设置为数组(这个数组包含特殊的mongoDB特别定义的变量名作为操作符,变量名对应的value作为操作对象)将会起到条件查询的作用。
就PHP本身的性质而言,由于其松散的数组特性,导致如果我们输入value=A那么,也就是输入了一个value的值为1的数据。如果输入value[ne=>2),在MongoDB的角度来,很有可能从原来的一个单个目标的查询变成了条件查询($ne表示不等于-not equel):
从xxx.find({'key': 'A'})变成了xxx.find({'key':{$ne:'A'}})
显然这样已经出现了非常严重的安全问题。
JSON查询以及数据格式
尽管相对安全,但流行的JSON表述格式仍可受到新类型的注入攻击。我们将举例说明MongoDB中的此类攻击,MongoDB是一个面向文档的数据库,已经有多个大型供应商予以采用,其中包括eBay、Foursquare和LinkedIn。在MongoDB中,查询和数据以JSON格式描述,这在安全方面要优于SQL,因为它是更充分定义的,容易进行加密和解密,而且在每种编程语言中都有不错的原生实现。像SQL注入那样对查询结构的破坏,在JSON结构的查询中会更难实现。在MongoDB中常见的插入语句应该是这样的:
db.books.insert({ title: 'The Hobbit', author: 'J.R.R. Tolkien' })
这会插入一个新的文档到books的集合中,它具有title(标题)和author(作者)字段。常见的查询条件应该是这样的:
db.books.find({ title: 'The Hobbit' })
除限制要查询的字段之外,查询中还可以包括正则表达式和条件。
PHP重言式注入
让我们审视一下图中所画的架构,一个使用PHP实现后端的Web应用,它将用于查询数据存储的请求编码为JSON格式。让我们使用一个MongoDB示例去演示数组注入漏洞吧,从技术和结果上来看这是一个与SQL注入有些类似的攻击手段。
图中使用MongoDB的PHP应用。一个使用PHP实现后端的Web应用,它把用于查询数据存储的请求编码为JSON格式。PHP编码数组为原生JSON。嗯,数组示例如下:
array('title' => 'The Hobbit', 'author' => 'J.R.R. Tolkien');
将由PHP编码为以下JSON格式:
{"title": "The Hobbit", "author": "J.R.R. Tolkien"}
如果一个PHP具有登录机制,由用户浏览器通过HTTP POST(它像HTTP GET一样容易受到攻击)发送过来用户和密码,常见的POST URL编码应该是这样的:
username=Tolkien&password=hobbit
后端PHP代码针对该用户对它进行处理并查询MongoDB,如下所示:
db->logins->find(array("username"=>$_ POST["username"], "password"=>$_POST["password"]));
这本身合情合理没什么问题,直觉上开发人员可能喜欢用以下查询:
db.logins.find({ username: 'tolkien', password: 'hobbit'})
然而,PHP针对关联数组有个内置的机制,这让攻击者有机可乘,可发送以下恶意的数据:
username[$ne]=1&password[$ne]=1
PHP会把该输入解析为:
array("username" => array("$[ne] " => 1), "password" => array("$ne" => 1));,
它会编码为如下MongoDB查询:
db.logins.find({ username: {$ne:1 }, password {$ne: 1 })
因为$ne是MongoDB用来判定条件是否不相等的,所以它会查询登录集合中的所有用户名称不等于1且密码也不等于1的记录。因此,本次查询将返回登录集合中的所有用户。换成SQL的表述法,就等同于以下查询语句:
SELECT * FROM logins WHERE username <> 1 AND password <> 1
在这种情况下,漏洞就为攻击者提供了一个不必有效凭证即可登录应用的方式。在其他变体中,该漏洞可能会导致非法数据访问或由无特权的用户执行特权操作。为缓解这个问题,我们需要转换从需求中接收的参数为适当类型,在本例中,可使用字符串,如下所示:
db->logins->find( array("username"=>(string)$_ POST["username"], "password"=>(string)$_ POST["password"]));
------------END--------------
加小唐老师微信领取
500元专属课程优惠券
+
领取最新技术资料和面试题