一、NoSQL攻击途径
可以大致分为以下5类:
1.重言式。又称永真式。此类攻击是在条件语句中注入代码,使生成的表达式判定结果永远为真,从而绕过认证或访问机制。
2.联合查询。联合查询是一种众所周知的SQL注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。联合查询最常用的用法是绕过认证页面获取数据。在本文中,我们将展示一个攻击示例,它将通过增加永真的表达式利用布尔OR运算符进行攻击,从而导致整个语句判定出错,进行非法的数据获取。
3.JavaScript注入。这是一种新的漏洞,由允许执行数据内容中JavaScript的NoSQL数据库引入的。JavaScript使在数据引擎进行复杂事务和查询称为可能。传递不干净的用户输入到这些查询中可以注入任意JavaScript代码,这会导致非法的数据获取或篡改。
4.背负式查询。在背负式查询中,攻击者通过利用转义特定字符(比如像回车和换行之类的结束符)插入由数据库额外执行的查询,这样就可以执行任意代码了。
5.跨域违规。HTTP REST APIs是NoSQL数据库中的一个流行模块,然而,它们引入了一类新的漏洞,它甚至能让攻击者从其他域攻击数据库。在跨域攻击中,攻击者利用合法用户和他们的网页浏览器执行有害的操作。在本文中,我们将展示此类跨站请求伪造(CSRF)攻击形式的违规行为,在此网站信任的用户浏览器将被利用在NoSQL数据库上执行非法操作。通过把HTML格式的代码注入到有漏洞的网站护着欺骗用户进入到攻击者自己的网站上,攻击者可以在目标数据库上执行post动作,从而破坏数据库。
二、JSON查询以及数据格式
尽管相对安全,但流行的JSON表述格式仍可受到新类型的注入攻击。举例说明MongoDB中的此类攻击,MongoDB是一个面向文档的数据库,已经有多个大型供应商予以采用,其中包括eBay、Foursquare和LinkedIn.
在MongoDB中,查询和数据以JSON格式描述,这在安全方面要优于SQL,因为它是更充分定义的,容易进行加密和解密,而且在每种编程语言中都有不错的原生实现。像SQL注入那样对查询结构的破坏,在JSON结构的查询中会更难实现。在MongoDB中常见的插入语句应该是这样的:
db.books.insert({ title: 'The Hobbit', author: 'J.R.R. Tolkien' })
常见的查询条件应该是这样的:
db.books.find({title: 'The Hobbit' })
除限制要查询的字段之外,查询中还可以包括正则表达式和条件。
三、PHP重言式注入
让我们审视一下架构,一个使用PHP实现后端的Web应用,它将用于查询数据存储的请求编码为JSON格式。让我们使用一个MongoDB示例去演示数组注入漏洞吧。
图3 使用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发送过来用户和密码,常见的POST URL编码应该是这样的:
username=Tolkien&password=hobbit
后端PHP代码针对该用户对它进行处理并查询MongoDB,如下所示:
db->logins->find(array("username"=>$_ POST["username"], "password"=>$_POST["password"]));
这本身合情合理,然而,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"]));
四、NoSQL联合查询注入
SQL注入漏洞经常是由于未对用户输入进行适当编码而直接拼接查询造成的。在MongoDB之类的流行数据存储中,JSON查询结构使攻击变得更难了。然而,这并不代表不可能。
让我们看一个通过HTTP POST发送用户名和密码参数到后端的登录表单,它通过拼接字符串的方式得到查询语句。例如,开发人员可能这么做:
string query="{username: '" + post_username + "', password: '" + post_passport + ' "}"
具有有效输入时,得到的查询语句是应该这样的:
{ username: 'tolkien', password: 'hobbit' }
但具有恶意输入时,这个查询语句会被转换为忽略密码的,在无需密码的情况下登录用户账号。恶意输入示例如下:
username=tolkien', $or: [ {}, {'a':'a&password=' }],$comment: 'successful MongoDB injection'
该输入会被构建到该查询中:
{ username: 'tolkien', $or: [ {}, {'a':'a', password '' }], $comment:'successful MongoDB injection' }
只要用户名是正确的,这个查询就可以成功。转换成SQL的表述,这个查询类似于以下语句:
SELECT * FROM logins WHERE username='tolkien' AND (TRUE OR ('a'='a' AND password = ''))
密码称为这个查询多余的一部分,因为()内的条件总为真,所以不会影响到查询的最终结果。这是怎么发生的呢?以下为拼接出的查询串,用户输入为加粗字体,剩余的文本串为无格式字体:
{ username: 'tolkien', $or: [ {}, {'a':'a',password '' }], $comment: 'successful MongoDB injection' }
这个攻击在任何只要用户名正确的情况下都将成功,一般得到个用户名并不是什么难事。
五、NoSQL JavaScript注入
NoSQL数据库中有个共同特性,那就是可以在数据库引擎中运行JavaScript,从而可以执行复杂的查询或MapReduce之类的事务。包括MongoDB和CouchDB及其后续的Cloudant和BigCouch等流行的数据库都允许这么做。如果不干净的用户输入发现这种查询方式的话,这么执行JavaScript就等于把薄弱面暴露给攻击者了。例如,设想一个需要JavaScript代码的复杂事务,包含有不干净的用户输入作为查询的参数。让我们看一下它的存储模型,它保存了一组条目,每个条目具有价格和数量属性。为得到这些属性的总数或平均值,开发人员编写了一个MapReduce函数,它从用户那里接收数量或价格作为参数,然后进行处理。在PHP中,看起来是如下代码($param是用户输入):
$map="function() {
for(var i=0;i<this.items.length; i++) <br> {
emit(this.name, this.items[i].$param);
}
}";<br>
$reduce="function(name, sum) {
return Array.sum(sum);
}";<br>
$opt="{out:'totals' }";<br>
$db->execute("db.stores.mapReduce($map, $reduce, $opt);");
这段代码把每个条目按名称给定的$param合计起来。当时,$param预期是接受数量(amount)或价格(price)的,这段代码将按预期进行运转。但是,因为用户输入未被定义,所以恶意输入(它可能包含任意JavaScript)将被执行。
看一下如下输入:
function(kv) {
return 1;
},{ out:'x'});
db.injection.insert({success:1});return 1;
第一部分的数据会闭合最初的MapReduce函数,然后攻击者就可以在数据库上执行想要的JavaScript了(加粗部分)。最终,最后一部分调用一个新的MapReduce以保持被注入代码的原始语句的平衡。在把会被执行的用户输入合并到为字符串后,我们得到以下代码(注入的用户输入加粗显示):
db.stores.mapReduce(function() {
for (var i = 0; i < this.items.length; i++) {
emit(this.name, this.items[i].a);
}
},<br>
function(kv) {
return 1;
}, { out: 'x' }); <br>
db.injection.insert({success:1});
return 1;
db.stores.mapReduce(function() {
<br>{
emit(1,1);
}
}, function(name, sum) {
return Array.sum(sum);
}, { out: 'totals' });"
这个注入看起来和经典的SQL注入非常相似。防御此类攻击的一种方式是在数据库配置中禁止执行JavaScript。如果JavaScript是必需的,那么最好的策略是不使用任何用户输入。
六、键值对数据存储
像Memcached、Redis和Tachyon之类的键值对存储是内存数据存储,旨在加快应用、云架构和平台以及大数量框架的执行速度。这些平台考虑的是反复频繁访问的数据的存储和检索。它们通常处于数据存储之前,如图所示。缓存架构经常存储认证令牌及容器访问控制列表,对于每个后续的用户请求必须重新使其生效。
图4 分布式内存数据存储架构。被攻击的Web服务器使用一个键值数据存储进行快速数据检索。对数据存储的查询是在该Web服务器上通过用户提供的数据构建出来的。
尽管由于键值对查询很简单所以通常缓存API也非常简单,但我们发现一个Memcached潜在的注入攻击手段,那就是基于特定PHP版本的Memcached驱动程序中的漏洞。达成以下条件即可进行攻击:
1.用作传递给换粗nset/get的属性(例如,value)是来自于用户请求的信息(例如,HTTP标头)
2.接收到的字符串未经过处理就发送了
3.缓存的属性包括将导致查询执行不同于预期的行为的敏感信息。
如果满足这些条件,攻击者就可以注入查询或操纵查询逻辑,比如背负式查询攻击。
七、背负式查询
把一个键及相应的值加到使用Memcached的数据库中的一组操作。当从命令行界面调用时,这组函数使用两行输入,第一行是:
set <KEY> <FLAG> <EXPIRE_TIME> <LENGTH>,
然后第二行由要保存的数据构成。当PHP配置的函数被调用时,它接收的两个参数看起来是这样的:
$memcached->set('key', 'value');
研究人员表示,该驱动程序未能对带有回车\r(0x0D)和换行的\n(0x0A)的ASCII码采取措施,导致攻击者有机会注入包含有键参数的新命令行和其他非计划内的命令道缓存中。
看一下如下代码,其中$param是用户输入并作为键来作用:
$memcached=new Memcached();
$memcached->addServer('localhost',11211);
$memcached->set($param,"some value");
攻击者可以提供以下输入进行注入攻击:
"key1 0 3600 4\r\nabcd\r\nset key2 0 3600 4\r\ninject\r\n"
在本例中,增加到数据库中的第一个键是具有"some value"值得key1。攻击者可以增加其他的、非计划内的键到数据库中,即带有"inject"值得key2.
这种注入也可以发生在get命令上。让我们看一下Memcached主页上的示例,它以这三行开头:
Function get_foo(foo_id)
foo = memcached_get("foo: " . foo_id)
return foo
if defined foo
这个示例展示了Memcached的典型用户,在处理输入之前首先检查在数据库中是不是已经存在了。假设用类似代码检查从用过户那里接受的认证令牌,验证他们是不是登录过了,那么就可以通过传递一下作为令牌的字符串来利用它:
"random_token\r\nset my_crafted_token 0 3600 4\r\nroot\r\n"
当这个字符串作为令牌传递时,数据库将检查这个"random_token"是否存在,然后将添加一个具有"root"值的"my_crafted_token"。之后,攻击者就可以发送具有root身份的my_crafted_token令牌了。
可以被这项技术攻击的其他指令还有:
incr <Key> <Amount>
desc <Key> <Amount>
delete <Key>
在此,incr用于增加一个键的值,decr用于缩减一个键的值,以及delete用于删除一个键。攻击者也可以用像set和get函数一样的手段来使用带来自己键参数的这三个函数。
攻击者可以使用多条目函数进行同样的注入:deleteMulti、getMulti和setMulti,其中每一个键字段都可以被注入。
回车换行注入可以被用于连接多个get请求。
该驱动程序的漏洞已经在PHP 5.5中修复,但不幸的是它已经存在于之前所有的PHP版本中了。按照W3Techs.com对生产系统的PHP版本的统计来看,超过86%的PHP网站使用了比5.5要老的版本,这意味着如果他们使用了Memcached就很容易受到这种注入攻击。
八、跨域违规
NoSQL数据库的另一个常见特点是,他们能够常常暴露能够从客户端应用进行数据库查询的HTTP REST API。暴露REST API的数据库包括MongoDB、CouchDB和HBase。暴露REST API就直接把数据库暴露给应用了,甚至是仅基于HTML5的应用,因为它不再需要间接的驱动程序了,让任何编程语言都可以在数据库上执行HTTP查询。这种REST API给跨站点请求伪造(CSRF)暴露了数据库,让攻击者绕过了防火墙和其他外围防御。
九、缓解
鉴于我们在本文中所提到的这些攻击手段,NoSQL部署中的安全问题的缓解是非常重要的。但不幸的是,应用层的代码分析不足以确保所有风险都能得以缓解。三个趋势使该问题将比之前面临更多的挑战。首先,云和大数据系统的形成,它们通常会执行多个复杂应用,这些应用使用异构的开源工具和平台。而这些应用通常由开源社区开发,大多数情况下,未经受过全面的安全性测试。另一个挑战是伴随DevOps方法论而形成的现代代码开发的速度,因为DevOps追求的是缩短开发和生产之间的时间。最后,大多数应用安全测试工具不能与新编程语言的应用保持同步,例如,大多数安全产品不支持Golang、Scala和 Haskel。