Zum Inhalt springen

NoSQL Injection

CWE-943OWASP A03:2021Aktualisiert 31. August 20266 Min. Lesezeit

NoSQL Injection ist eine Schwachstelle, bei der ein Angreifer die Bedeutung einer Datenbankabfrage verändert, indem er statt eines Wertes einen Abfrageoperator mitsendet, etwa $ne oder $gt in einer JSON-Anfrage an MongoDB. Damit umgeht er eine Anmeldeprüfung oder liest Dokumente aus, die nicht für ihn bestimmt sind. Abhilfe schaffen Schemavalidierung und eine explizite Umwandlung der Eingabe in den erwarteten Typ.

NoSQL-Datenbanken wie MongoDB speichern keine Zeilen in Tabellen, sondern Dokumente, und sie sprechen kein SQL. Das Injection-Problem verschwindet dadurch nicht, es ändert nur seine Form: Statt eines SQL-Fragments schleust ein Angreifer einen Abfrageoperator ein. Im Folgenden erfahren Sie, wie das abläuft, was ein Angreifer damit erreicht und wie Sie es verhindern.

Was ist NoSQL Injection?

NoSQL Injection ist eine Schwachstelle, bei der ein Angreifer die Bedeutung einer Datenbankabfrage verändert, indem er eine Eingabe mitsendet, die die Anwendung als einfachen Wert erwartet, die in Wirklichkeit aber ein Stück Abfragesprache ist. NoSQL steht für “Not only SQL”, ein Sammelbegriff für Datenbanken, die ohne Tabellen und ohne SQL arbeiten. MongoDB ist davon die mit Abstand bekannteste.

Der Unterschied zur SQL Injection liegt in der Struktur. Eine SQL-Abfrage ist eine Textzeile, in die ein Angreifer Text einfügt. Eine MongoDB-Abfrage ist kein Text, sondern ein strukturiertes Objekt: eine Menge von Feldern mit Werten, wobei ein Wert auch ein Operator sein darf, etwa $ne (ungleich) oder $gt (größer als). Sobald eine Benutzereingabe unmittelbar als Wert in dieses Objekt gelangt und diese Eingabe selbst ein Objekt ist, liest die Datenbank sie als Operator statt als Datum.

Ein Vergleich aus dem Alltag: An einem Schalter füllen Sie ein Formular mit Nachname und Geburtsdatum aus. Jemand schreibt in das Feld für das Geburtsdatum jedoch kein Datum, sondern den Satz “egal, jedes Datum passt”. Ein Mitarbeiter, der das Formular wörtlich in das Suchsystem überträgt, bekommt daraufhin alle Personen angezeigt.

Genau deshalb sind JSON-Schnittstellen der natürliche Angriffspunkt: Ein HTML-Formular liefert immer Text, JSON dagegen kennt den Unterschied zwischen einer Zeichenkette und einem Objekt und gibt ihn unverändert an Ihren Code weiter.

Wie funktioniert ein NoSQL-Injection-Angriff?

Nehmen wir einen Anmelde-Endpunkt in Node.js, der die Anfrage als JSON entgegennimmt und die beiden Felder ungeprüft in eine MongoDB-Abfrage übernimmt.

Verwundbar:

// Die Felder aus req.body stammen direkt aus dem JSON des Aufrufers
app.post("/api/login", async (req, res) => {
  const user = await db.collection("users").findOne({
    email: req.body.email,
    password: req.body.password
  });

  if (user) {
    req.session.userId = user._id;
    return res.json({ ok: true });
  }
  res.status(401).json({ error: "invalid credentials" });
});

Bei einer normalen Anmeldung sendet der Browser zwei Zeichenketten, und alles funktioniert wie vorgesehen. Ein Angreifer sendet jedoch keine Zeichenketten, sondern zwei kleine Objekte:

POST /api/login HTTP/1.1
Host: app.example.com
Content-Type: application/json

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

Die Abfrage, die MongoDB nun ausführt, sucht nach einem Dokument, dessen E-Mail-Adresse ungleich null ist und dessen Passwort ebenfalls. Das trifft auf nahezu jeden Benutzer der Collection zu. findOne liefert den ersten Treffer zurück, die Anwendung sieht einen gefundenen Benutzer und legt eine Sitzung an. Der Angreifer ist drin, ohne eine einzige gültige Angabe. Trägt er zusätzlich die echte E-Mail-Adresse eines bekannten Administrators ein und belässt nur das Passwort als Operator, sucht er sich das übernommene Konto sogar selbst aus.

Es bleibt nicht bei der Anmeldung. Mit dem Operator $regex lässt sich ein Passwort-Hash, ein Reset-Code oder ein API-Schlüssel Zeichen für Zeichen rekonstruieren: Der Angreifer fragt wiederholt, ob der gespeicherte Wert mit einem bestimmten Muster beginnt, und liest die Antwort am Unterschied in der Serverantwort ab. Das ist dieselbe Vorgehensweise wie bei einer Blind SQL Injection, nur mit anderen Bausteinen.

Die Lösung besteht aus zwei Schritten, die einander ergänzen. Prüfen Sie die Anfrage gegen ein explizites Schema, bevor sie die Datenbank erreicht, und stellen Sie sicher, dass jeder Wert den erwarteten Typ hat.

Sicher:

import { z } from "zod";

const LoginBody = z.object({
  email: z.string().email().max(254),
  password: z.string().min(8).max(200)
});

app.post("/api/login", async (req, res) => {
  const parsed = LoginBody.safeParse(req.body);
  if (!parsed.success) {
    return res.status(400).json({ error: "invalid input" });
  }

  // Nur eine Zeichenkette erreicht die Abfrage; ein Objekt kommt nicht durch
  const email = String(parsed.data.email).toLowerCase();
  const user = await db.collection("users").findOne({ email });

  if (!user || !(await bcrypt.compare(parsed.data.password, user.passwordHash))) {
    return res.status(401).json({ error: "invalid credentials" });
  }

  req.session.userId = user._id;
  res.json({ ok: true });
});

Zwei Dinge haben sich geändert. Das Schema weist jede Anfrage zurück, in der die E-Mail-Adresse oder das Passwort keine Zeichenkette ist, sodass $ne gar nicht erst an der Haustür ankommt. Und das Passwort wird nicht mehr in der Abfrage verglichen, sondern anschließend gegen einen Hash, womit die Datenbank nicht länger über die Anmeldung entscheidet. Die explizite Umwandlung mit String() ist die letzte Leitplanke für Werte, die aus anderen Quellen stammen.

Schlüssel herauszufiltern, die mit einem Dollarzeichen beginnen, ist ein Pflaster und keine Lösung: Eine Sperrliste deckt nur die Muster ab, an die Sie gedacht haben, und beschädigt legitime Eingaben. Beachten Sie außerdem, dass sich das nicht auf JSON beschränkt. Mit dem erweiterten Query-Parser von Express wird auch eine URL wie ?email[$ne]=x zu einem verschachtelten Objekt.

Welche Auswirkungen hat NoSQL Injection?

Der Schweregrad hängt davon ab, an welcher Stelle die Eingabe in die Abfrage gelangt, und die Spannweite ist groß. Sitzt die Schwachstelle in einer Anmeldeprüfung, ist die Folge unmittelbar und vollständig: Ein Angreifer meldet sich ohne Passwort an, auf Wunsch als Administrator, und erbt sämtliche Rechte dieses Kontos. Sitzt sie in einer Such- oder Filterfunktion, fällt die Folge unauffälliger aus, ist aber nicht weniger schwerwiegend: Ein Filter, der nur die eigenen Dokumente liefern sollte, lässt sich auf die gesamte Collection ausweiten, einschließlich der Daten anderer Mandanten in einer geteilten Umgebung.

Hinzu kommt das gezielte Abgreifen von Daten. Ein Angriff über $regex erlaubt die Rekonstruktion genau jener Felder, die am wertvollsten sind: Passwort-Hashes, Sitzungstoken, Reset-Codes. Erreicht die Eingabe eine Update-Operation, lassen sich Dokumente zudem verändern, etwa um die eigene Rolle anzuheben.

Am oberen Ende der Spannweite steht die Ausführung von Code. Konstrukte wie $where und mapReduce werten JavaScript auf dem Datenbankserver aus, sodass eine Injection dort bis zum vollständigen Auslesen des Datenbestands oder zu einem Denial of Service durch eine Endlosschleife führen kann. Deshalb reicht der Schweregrad von hoch bis kritisch, und ein bestätigter Fall löst in aller Regel die Meldepflicht nach der DSGVO aus.

Wie erkennen Sie NoSQL Injection?

Der Kern der Prüfung ist die Manipulation von Typen: Senden Sie überall dort, wo die Anwendung eine Zeichenkette erwartet, ein Objekt und vergleichen Sie die Antwort. Eine Anfrage, in der ein Feld einen Operator wie $ne oder $gt trägt und die plötzlich eine erfolgreiche Anmeldung, mehr Treffer oder eine deutlich längere Antwortzeit liefert, ist ein starkes Indiz. Ebenso ein Serverfehler mit einem Mongo-Stacktrace, denn er belegt, dass die Eingabe die Abfrage ungehindert erreicht hat.

Automatisierte Scanner übersehen das häufig, und zwar aus einem nachvollziehbaren Grund: Sie verändern den Text in einem Parameter, nicht dessen Typ. Ein Scanner, der ' OR 1=1 an jeden Wert anhängt, kommt bei einer JSON-Schnittstelle nie über eine abgelehnte Zeichenkette hinaus. Ein Code-Review schließt diese Lücke schnell: Suchen Sie nach Aufrufen von find und findOne, in denen ein Wert aus req.body oder req.query unmittelbar im Filter steht. Bei einem Penetrationstest untersucht AssistSec diese typabhängigen Fälle manuell, gerade weil sie durch automatisierte Prüfungen hindurchrutschen.

Wie verhindern Sie NoSQL Injection?

  • Validieren Sie jede Anfrage gegen ein explizites Schema mit einer Bibliothek wie zod, Joi oder ajv, und weisen Sie unbekannte Felder und falsche Typen ab, bevor die Anwendung mit den Daten arbeitet.
  • Wandeln Sie Werte explizit in den erwarteten Typ um. Ein String() oder Number() unmittelbar vor der Abfrage macht es unmöglich, dass noch ein Objekt im Filter landet.
  • Verwenden Sie ein ODM mit striktem Schema wie Mongoose und vermeiden Sie Felder vom Typ Mixed sowie selbst zusammengesetzte Filterobjekte.
  • Vergleichen Sie Passwörter und Token niemals in der Datenbank. Holen Sie das Dokument über ein identifizierendes Feld und prüfen Sie das Geheimnis anschließend in der Anwendung gegen einen Hash.
  • Deaktivieren Sie serverseitiges JavaScript auf dem Datenbankserver, wenn Sie $where und mapReduce nicht benötigen, und beschränken Sie die Rechte des Datenbankbenutzers auf das, was die Anwendung tatsächlich braucht.
  • Geben Sie niemals rohe Datenbankfehler an den Benutzer aus. Protokollieren Sie sie intern; ein Stacktrace verrät einem Angreifer genau, wo seine Eingabe ankommt.
  • Lassen Sie die Schnittstelle regelmäßig testen. Ein gezielter Penetrationstest und ein Code-Review fangen die typabhängigen Fälle ab, die Scanner überspringen.

Quellen

Häufige Fragen

Ist NoSQL Injection weniger gefährlich als SQL Injection?

Nein. Die Form des Angriffs unterscheidet sich, die Folgen nicht. Eine erfolgreiche NoSQL Injection bedeutet ebenso eine umgangene Anmeldung, eine ausgelesene Collection oder ein verändertes Dokument. In manchen Konfigurationen reicht sie sogar weiter, weil einzelne NoSQL-Datenbanken JavaScript auf dem Server ausführen können.

Schützt Mongoose vor NoSQL Injection?

Weitgehend ja, denn ein Mongoose-Schema wandelt Werte in den deklarierten Typ um und weist ab, was nicht passt. Dieser Schutz entfällt, sobald Sie einen Filter selbst aus rohen Eingaben zusammensetzen, ein Feld vom Typ Mixed verwenden oder den strikten Modus abschalten.

Welche MongoDB-Operatoren nutzen Angreifer am häufigsten?

In der Praxis sind das $ne und $gt, um einen Vergleich immer wahr zu machen, $regex, um einen Wert Zeichen für Zeichen zu rekonstruieren, $in, um viele Kandidaten auf einmal zu testen, und $where, um JavaScript auf dem Server auszuführen.

Reicht es, Dollarzeichen aus der Eingabe zu entfernen?

Nur bedingt. Das blockiert die naheliegendsten Payloads, ist aber eine Sperrliste, und Sperrlisten sind nie vollständig. Zudem beschädigt es legitime Eingaben. Typprüfung und Schemavalidierung beheben die Ursache.

Sind JSON-Schnittstellen stärker gefährdet als HTML-Formulare?

Ja. Ein klassisches HTML-Formular liefert immer Text, während JSON den Unterschied zwischen einer Zeichenkette und einem Objekt erhält und unverändert an Ihren Code weitergibt. REST- und GraphQL-Endpunkte mit JSON sind deshalb der übliche Einstiegspunkt.

Verwandte Artikel

Zum Suchen / drücken · Esc