摘自:http://www.infoq.com/cn/articles/nosql-injections-analysis
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”]));
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 = ‘’)) #successful MongoDB injection
密码成为这个查询多余的一部分,因为()内的条件总为真,所以不会影响到查询的最终结果。
这是怎么发生的呢?以下为拼接出的查询串,用户输入为加粗字体,剩余的文本串为无格式字体:
{ username: ‘tolkien’, $or: [ {}, { ‘a’: ‘a’, password ‘’ } ], $comment: ‘successful MongoDB injection’ }
这个攻击在任何只要用户名正确的情况下都将成功,一般得到个用户名并不是什么难事。
NoSQL JavaScript注入
$map = “function() { for (var i = 0; i < this.items.length; i++) { emit(this.name, this.items[i].$param); } }”; $reduce = “function(name, sum) { return Array.sum(sum); }”; $opt = “{ out: ‘totals’ }”; $db->execute(“db.stores. mapReduce($map, $reduce, $opt);”);
这段代码把每个条目按名称给定的$param合计起来。当时,$param预期是接收数量(amount)或价格(price)的,这段代码将按预期进行运转。但是,因为用户输入未被转义,所以恶意输入(它可能包含任意JavaScript)将被执行。
看一下如下输入:
a);}},function(kv) { return 1; }, { out: ‘x’ });db.injection. insert({success:1});return 1;db.stores.mapReduce(function() { { emit(1,1
第一部分的数据会闭合最初的MapReduce函数,然后攻击者就可以在数据库上执行想要的JavaScript了(加粗部分)。最终,最后一部分调用一个新的MapReduce以保持被注入代码的原始语句的平衡。在把会被执行的用户输入合并到为字符串后,我们得到以下代码(注入的用户输入加粗显示):
db.stores.mapReduce(function() { for (var i = 0; i < this.items.length; i++) { emit(this.name, this.items[i].a); } },function(kv) { return 1; }, { out: ‘x’ }); db.injection.insert({success:1}); return 1;db.stores. mapReduce(function() { { emit(1,1); } }, function(name, sum) { return Array. sum(sum); }, { out: ‘totals’ });”
这个注入看起来与经典的SQL注入非常相似。防御此类攻击的一种方式是在数据库配置中禁止执行JavaScript。如果JavaScript是必需的,那么最好的策略是不使用任何用户输入。
背负式查询
把一个键及相应的值加到使用Memcached的数据库中的一组操作。当从命令行界面调用时,这组函数使用两行输入,第一行是:
set <KEY> <FLAG> <EXPIRE_TIME> <LENGTH>,
然后第二行由要保存的数据构成。
当PHP配置的函数被调用时,它接收的两个参数看起来是这样的:
$memcached->set(‘key’, ‘value’);
研究人员表示,该驱动程序未能针对带有回车\r(0x0D)和换行的\n(0x0A)的ASCII码采取措施,导致攻击者有机会注入包含有键参数的新命令行和其他非计划内的命令到缓存中8。
看一下如下代码,其中的$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命令上。