原文链接:https://arxiv.org/pdf/1506.04082

发表在W2SP 2015,In Proceedings of the 9th Workshop on Web 2.0 Security and Privacy (W2SP) 2015

背景知识

保证数据库安全一直是保证系统安全的重要方面,对企业数据库的访问会使攻击者可以控制企业的一些隐私数据。例如攻击者可以将恶意代码插入到数据库中,这使攻击者几乎可以对数据执行任何操作,包括访问未经授权的数据及更改、删除和插入新数据。尽管由于安全框架的使用和开发者对SQL注入防御意识的提高,SQL注入的利用多年来一直在稳步下降,但是SQL注入一直是最流行的攻击手段之一。而NoSQL数据库的出现,比如MongoDB,Redis和Cassandra,因为使用不同的查询语言,使传统的SQL注入方式变得不再可用。但是作者发现,这并不意味着NoSQL数据库就不会受到SQL注入漏洞的影响了。在本篇文章中,作者介绍了几种新的注入技术,比如:

  • PHP数组注入攻击(PHP array injection attack);
  • MongoDB OR注入(MongoDB OR injection);
  • 任意JavaScript注入(arbitrary JavaScript injection)。

NoSQL注入方式

1. NoSQL语法简介

以比较流行的NoSQL数据库MongoDB为例,MongoDB是一个面向文档的数据库,被许多的大公司采用,比如EBay,Foursquare和LinkedIn等。

MongoDB的查询语句和数据都表现为JSON格式,这比传统的SQL语句更加安全。以下为MongoDB的insert语句:

1
2
3
4
db.books.insert({
title: 'The Hobbit',
author: 'J.R.R. Tolkien'
})

查询语句为:

1
2
3
db.books.find({
title: 'The Hobbit'
})

2. PHP数组注入攻击

下图所示的是PHP Web应用程序的一个架构:

1

PHP的array方法可以直接将数组转变成JSON:

1
array('title' => 'The hobbit', 'author' => 'J.R.R. Tolkien');

结果为:

1
2
3
4
{
"title": "The hobbit",
"author": 'J.R.R. Tolkien'
}

但在PHP应用程序中,我们经常会通过HTTP GET/POST请求传递参数,比如:

1
username=tolkien&password=hobbit

后端对应的数据库查询语句可能是:

1
db->logins->find(array("username"=>$_POST["username"],"password"=>$_POST["password"]));

对应的MongoDB原生数据库语义为:

1
db.logins.find({ username: 'tolkien', password: 'hobbit' })

但是PHP有一个内置的关联数组机制,允许攻击者发送以下恶意payload:

1
username[$ne]=1&password[$ne]=1

PHP会将该payload转变为:

1
2
3
4
array(
"username" => array("$ne" => 1),
"password" => array("$ne" => 1)
);

对应的MongoDB原生查询语句为:

1
db.logins.find({ username: { $ne: 1 }, password: { $ne: 1 } })

但是在MongoDB的语法中,$ne表示_not equal_,所以这条语句就相当于:

1
SELECT * FROM logins WHERE username <> 1 AND password <> 1

在这样的场景下,攻击者根本不需要提供正确的用户名和密码就能很轻松的登录目标站点了。

除了$ne之外,MongoDB中还存在其他的符号,比如:[$lt][$gt][$regex]等。

3. NoSQL OR注入

SQL注入漏洞的一个常见原因是从字符串文本构建查询,其中包括未使用适当编码的用户输入。虽然这种注入方式因为JSON查询而变得更难实现,但是也不是完全没有可能的。

一些开发者可能采取这样的方式将用户输入转成JSON,而不是使用PHP自带的array函数:

3

在正常情况下,拼接后可以得到:

1
{ username: 'tolkien', password: 'hobbit' }

如果攻击者构造这样的恶意输入:

2

拼接后的结果为:

4

$or就表示对后面的[]中的内容进行OR语句操作,而一个{}查询语句永远返回TRUE

所以这条语句就相当于:

1
SELECT * FROM logins WHERE username = 'tolkien' AND (TRUE OR ('a' = 'a' AND password = '')) #successful MongoDB injection

只要用户能够提供正确的用户名就可以直接登录,而不需要密码校验。

4. NoSQL JavaScript注入

NoSQL数据库的另一个特性是可以执行JavaScript语句。如果用户的输入为转义或未充分转义,则Javascript执行会暴露一个危险的攻击面。 例如,一个复杂的事物可能需要javascript代码,其中包括一个未转义的用户输入作为查询中的一个参数。

比如以一个商店为例,商店中有一系列商品,每个商品都有价格和金额。开发人员想要获取这些字段的总和或者平均值,开发者编写了一个map reduce函数,其中$param参数接受用户的输入:

5

因为没有对用户的输入进行充分的过滤,所以攻击者可以构造这样的payload:

6

上面代码中绿色的部分的作用是闭合function()函数;红色的部分是攻击者希望执行的任意代码。最后最一部分蓝色的代码调用一个新的map reduce函数,以平衡注入到原始语句中的代码。

得到的效果为:

7

如果要防止JavaScript注入攻击,可以直接禁止数据库语句中JavaScript语句的执行(在_mongod.conf_中将javascriptEnabled设为false)或者是加强对用户输入的过滤。

5. 防御措施

作者主要提出了以下几种防御措施:

  1. 可以对程序的源代码进行静态分析或是Dynamic Application Security Testing,找出潜在的NoSQL注入漏洞点;
  2. 实现完备的访问控制来防御越权攻击。因为一些NoSQL数据库没有实现正确的用户验证authentication和用户授权验证RBAC authorization,这也会导致数据库中的数据受到危害。

参考资料

  1. https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/NoSQL%20Injection

  2. https://www.acunetix.com/blog/web-security-zone/nosql-injections/