NoSQL-Datenbanken haben eigene Injection-Klassen, die anders aussehen als SQL-Injection. Statt String-Konkatenation in SQL-Statements ist die häufigste Schwachstelle die direkte Übergabe von JSON-Objekten mit User-kontrollierten Operator-Feldern ($where, $ne, $gt, $regex). MongoDB ist der häufigste Fall — die Konzepte gelten aber analog für CouchDB, Elasticsearch und andere dokument-orientierte Datenbanken.
Wie NoSQL-Injection funktioniert
MongoDB-Queries sind JSON-Objekte:
// Standard-Query
db.users.findOne({ username: 'alice', password: 'secret' });Wenn eine Anwendung User-Input als Objekt entgegennimmt und ungesäubert in die Query gibt, kann ein:e Angreifer:in Operator-Felder einschleusen.
Beispiel-Schwachstelle:
// Schadhaft
app.post('/login', async (req, res) => {
const user = await db.users.findOne({
username: req.body.username,
password: req.body.password,
});
if (user) {
// Login erfolgreich
}
});Wenn req.body ein Objekt mit Operator-Werten enthält, wird der Login zur Lotterie:
{
"username": "admin",
"password": { "$ne": "x" }
}MongoDB interpretiert: „Username = admin UND Password ungleich 'x'". Da das Passwort kein 'x' ist, matcht jedes User-Konto. Login-Bypass mit drei Zeichen JSON.
Voraussetzung: Body-Parser akzeptiert verschachtelte Objekte (Express body-parser Default, Koa, FastAPI mit JSON-Schema, etc.).
Die gefährlichsten MongoDB-Operatoren
$ne — Not Equal:
Wie oben gezeigt — { field: { $ne: 'x' } } matcht alles außer dem Wert 'x'. Klassischer Auth-Bypass.
$gt, $gte, $lt, $lte — Größer/Kleiner:
{ "username": "admin", "password": { "$gt": "" } }$gt: "" ist immer wahr für nicht-leere Strings — Login-Bypass.
$regex — Regex-Matching:
{ "username": "admin", "password": { "$regex": "^a" } }Mit Regex-Tricks kann ein:e Angreifer:in zeichenweise das Passwort extrahieren (Boolean-Blind-Variante).
$where — JavaScript-Eval:
Der gefährlichste Operator. $where lässt JavaScript im DB-Server laufen:
{ "$where": "this.password.match(/^a/) || sleep(5000)" }Mit $where ist im Prinzip Server-Side Code-Execution möglich (im Sandbox-Modus der MongoDB-Engine). MongoDB hat in neueren Versionen $where standardmäßig deaktiviert und empfiehlt, es niemals für User-Input zu nutzen.
$expr — Expressive Queries:
$expr erlaubt komplexere Aggregations-Ausdrücke und kann ähnliche Bypass-Pattern erzeugen, wenn aus User-Input zusammengebaut.
$lookup und Sub-Queries:
$lookup joint mit anderen Collections. Wenn der/die Angreifer:in $lookup injizieren kann, lassen sich Daten aus anderen Collections in das Ergebnis ziehen — analog zu SQL UNION-Based-Injection.
Schutz-Patterns
1. Strikte Schema-Validation auf Body-Ebene.
Bevor User-Input überhaupt in die Query fließt, wird er validiert: alle Felder müssen primitive Typen sein, keine Objekte/Operator-Felder.
Express mit Zod:
import { z } from 'zod';
const LoginSchema = z.object({
username: z.string().min(1).max(100),
password: z.string().min(1).max(200),
}).strict();
// .strict() lehnt unbekannte Properties ab — und Operator-Objekte sind keine Strings
app.post('/login', async (req, res) => {
const parsed = LoginSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).send('Invalid input');
}
const { username, password } = parsed.data;
// username und password sind GARANTIERT Strings — kein Operator-Bypass
const user = await db.users.findOne({ username, password });
// ...
});Schema-Validation ist die wirksamste und einfachste Schutz-Schicht.
2. Mongoose strictQuery: true.
Wenn Mongoose das ORM ist, hilft die strictQuery-Option:
mongoose.set('strictQuery', true);
// Mit strictQuery werden Felder, die nicht im Schema sind,
// aus der Query entfernt
User.findOne({ username: 'admin', $where: '...' });
// $where wird ignoriert, weil nicht im SchemaStand 2026 ist strictQuery: true Default in Mongoose 7+. Aber: das schützt nur vor unbekannten Top-Level-Feldern, nicht vor Operator-Werten innerhalb erlaubter Felder. Schema-Validation bleibt nötig.
3. Operator-Stripping-Middleware.
Ein häufiges Pattern: Middleware, die alle $-Felder aus Request-Bodies entfernt:
function sanitizeMongo(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
if (Array.isArray(obj)) return obj.map(sanitizeMongo);
const result = {};
for (const key in obj) {
if (key.startsWith('$')) continue; // Operator-Felder entfernen
if (key.includes('.')) continue; // Dot-Notation entfernen
result[key] = sanitizeMongo(obj[key]);
}
return result;
}
app.use((req, res, next) => {
req.body = sanitizeMongo(req.body);
req.query = sanitizeMongo(req.query);
next();
});Die Library express-mongo-sanitize macht das fertig — sehr verbreitet in Express-Setups. Stripping ist defensive Schicht; Schema-Validation bleibt die primäre.
4. Explizite Typen in der Query.
// Sicher: explizit String-Konvertierung
const username = String(req.body.username);
const password = String(req.body.password);
const user = await db.users.findOne({ username, password });
// String() macht aus Objekten "[object Object]" — Match scheitertSchnelle Verteidigung, aber nicht ausreichend als alleinige Schicht — bessere Option ist Schema-Validation.
5. $where global deaktivieren.
In MongoDB-Konfiguration:
// mongod.conf
security:
javascriptEnabled: falseDamit ist $where server-seitig deaktiviert — auch wenn ein:e Angreifer:in es einschleusen kann, läuft kein JS.
CouchDB
CouchDB nutzt JSON-basierte Queries mit Map-Reduce-Views. Injection-Pattern:
- Server-Side JavaScript-Functions in Views — bei User-konfigurierbaren Views potenziell Code-Eval.
- Mango Queries (CouchDB 2.0+) ähnlich zu MongoDB-Queries — gleiche Operator-Manipulation möglich.
_design-Documents mit User-Input — Code-Injection in Views.
Schutz: User-Input niemals direkt in View-Definitionen oder Mango-Queries. Schema-Validation, Allow-List für Query-Operatoren.
Elasticsearch
Elasticsearch hat eigene Injection-Klassen:
Query-DSL-Injection:
// Schadhaft
const query = JSON.parse(`{
"query": {
"match": {
"name": "${userInput}"
}
}
}`);
// Wenn userInput Anführungszeichen enthält, bricht die Struktur
// Sicher: strukturiertes Objekt bauen
const query = {
query: { match: { name: userInput } }
};Painless-Script-Injection:
Painless ist Elasticsearchs eingebaute Skript-Sprache. In Aggregations und script_score-Queries verwendbar. Wenn User-Input in ein Painless-Skript fließt:
{
"query": {
"script_score": {
"script": {
"source": "doc['score'].value * ${userMultiplier}"
}
}
}
}Wenn userMultiplier aus User-Input kommt und ungesichert eingebaut wird, kann ein:e Angreifer:in beliebiges Painless ausführen — RCE-nahe Möglichkeit. Schutz: params-Feld für User-Werte:
{
"query": {
"script_score": {
"script": {
"source": "doc['score'].value * params.multiplier",
"params": { "multiplier": 2.5 }
}
}
}
}params werden als Daten behandelt — kein Code-Eval.
Path-Manipulation in URLs:
Elasticsearch-API ist REST. URL-Parameter (index, type) sollten nicht aus User-Input ohne Allowlist konstruiert werden.
Redis
Redis hat sein eigenes Risiko-Profil:
Command-Injection in Redis-Clients:
Wenn ein Redis-Client raw-Commands aus User-Input baut, kann eine eingeschleuste Newline-Sequenz mehrere Commands triggern (Redis-Protokoll ist line-basiert):
// Schadhaft (hypothetisch — moderne Clients schützen)
client.set(`session:${userId}`, userData);
// Wenn userData newlines enthält, könnte Protokoll-Injection möglich seinModerne Redis-Clients (ioredis, node-redis, redis-py) protokollieren binär und sind dagegen geschützt. Schwachstellen historisch in selbstgebauten Clients.
LUA-Script-Injection:
Redis kann LUA-Skripte ausführen (EVAL). Wenn User-Input ungesäubert in ein LUA-Skript fließt — Code-Injection:
// Schadhaft
client.eval(`return redis.call('GET', '${userKey}')`, 0);
// Sicher: LUA-KEYS statt String-Konkat
client.eval(`return redis.call('GET', KEYS[1])`, 1, userKey);Module-Funktionen:
Redis-Module (RedisJSON, RediSearch) bringen eigene Query-Sprachen mit. Bei RediSearch z. B. Filter-Syntax, die mit User-Input gebaut werden kann — analog zu NoSQL-Injection.
Test-Strategien
Manuelle Tests:
Klassisches Test-Payload-Set für NoSQL-Injection in Login-Endpunkten:
// Login-Bypass
{ "username": "admin", "password": { "$ne": "x" } }
{ "username": "admin", "password": { "$gt": "" } }
{ "username": "admin", "password": { "$regex": ".*" } }
// Daten-Discovery
{ "username": { "$ne": "x" }, "password": { "$ne": "x" } }
// $where-Test (wenn aktiviert)
{ "$where": "this.role === 'admin'" }Automatisierte Tools:
- NoSQLMap — analog zu sqlmap, aber für MongoDB/CouchDB.
- Burp Suite Pro mit NoSQL-Plugin.
- OWASP ZAP mit NoSQL-Add-On.
Statische Analyse:
- Semgrep-Regeln für
findOne-mit-direktem-Body,$where-Nutzung. - CodeQL mit JavaScript-Security-Pack.
- eslint-plugin-security für Node.js-spezifische Patterns.
Code-Review-Pattern:
Suche nach:
req.bodyoderreq.querydirekt infindOne,findAndUpdate,aggregate.$where-Verwendung im Code.- Ungetypte Function-Argumente, die in Mongo-Queries fließen.
Reale Vorfälle
Diaspora (2018) — MongoDB-Injection in der Open-Source-Social-Network-Plattform. Operator-basierter Bypass von Auth-Checks. Patch innerhalb von Tagen.
Reddit-Mod-Tools (2022) — interne Tools mit MongoDB-Backend; Bug-Bounty-Report über Operator-Manipulation in Filter-Funktionen. Auszahlung im vierstelligen Bereich.
MongoDB Atlas — Misconfiguration-Wellen statt direkter Injection: viele MongoDB-Instanzen sind offen im Internet, ohne Authentifizierung. Massendurchsuchungen seit 2017 (MongoDB-Ransomware-Wellen). Strenggenommen keine Injection, aber verwandtes Risiko-Cluster.
npm-Pakete mit NoSQL-Injection-CVEs — diverse kleinere Libraries, die Mongo-Queries aus User-Input bauen, haben über die Jahre CVEs gesammelt. Z. B. mongo-sanitize selbst hatte einmal eine Bypass-Schwäche.
Im Vergleich zu SQL-Injection sind NoSQL-Vorfälle weniger spektakulär, aber regelmäßig in Pentest-Reports. Wer mit MongoDB arbeitet und Body-Parser ungeschützt nutzt, hat hohes Risiko.
Besonderheiten
Operator-Felder sind das Kernproblem
SQL hat eine separate Query-Sprache; MongoDB nutzt JSON-Objekte. Das Problem: wenn der Body-Parser User-JSON-Objekte direkt entgegennimmt und das ORM sie als Query nutzt, ist die Trennung zwischen Daten und Operator-Anweisungen verloren. Schema-Validation reduziert User-Input strikt auf erwartete Daten-Typen.
$where ist faktisch deprecated
MongoDB selbst empfiehlt seit Jahren, $where nicht mehr zu nutzen — wegen Performance und Security. Seit MongoDB 4.4 ist es opt-in (security.javascriptEnabled). Wer $where in seinem Code findet: refactor zu nativer Query oder Aggregation.
Mongoose strictQuery in 6 → 7 → 8
Mongoose hat die Default-Einstellung mehrfach gewechselt. In Mongoose 6 war strictQuery per Default false; seit Mongoose 7 ist es true; Mongoose 8 macht es noch strikter. Bei Upgrades: Tests prüfen, ob alte Queries weiter funktionieren.
express-mongo-sanitize hat ältere Versionen mit Bugs
Die populäre Library hatte historisch Bypass-Schwächen — z. B. wenn Operator-Felder als Werte (nicht als Keys) auftauchten. Aktuelle Versionen sind gehärtet. Defense-in-Depth: nicht alleine darauf vertrauen.
GraphQL über MongoDB: doppelte Injection-Schicht
Wenn GraphQL-Backend mit MongoDB als Storage, sind beide Schichten Injection-relevant: GraphQL-Query-Manipulation (siehe GraphQL-Sicherheit Kap 18) PLUS Mongo-Operator-Injection im Resolver. Beide getrennt zu prüfen.
Aggregation-Pipeline-Injection ist subtiler
MongoDB-Aggregation-Pipelines ($match, $group, $lookup, ...) können ähnliche Probleme haben wie normale Find-Queries. Bei User-konfigurierbaren Filtern im Aggregation-Modus oft übersehen.
Elasticsearch Painless ist Sandboxed — aber nicht risikofrei
Painless läuft in einer Sandbox, die OS-Calls blockiert. Aber: Daten-Exfiltration über Aggregations-Ergebnisse, Information-Disclosure über Fehlermeldungen, DoS über Endlos-Schleifen sind weiter möglich. params-Feld konsequent nutzen.
Weiterführende Ressourcen
Externe Quellen
- OWASP — Testing for NoSQL Injection
- PortSwigger Web Security Academy — NoSQL Injection
- NoSQLMap
- MongoDB — Schema Validation
- express-mongo-sanitize
- Mongoose — strictQuery
- Elasticsearch — Painless
- PayloadsAllTheThings — NoSQL