Aikido

Prisma und PostgreSQL anfällig für NoSQL-Injection? Ein überraschendes Sicherheitsrisiko erklärt

|
#

Einleitung

Stellen Sie sich vor, Sie entwickeln eine Blogging-Web-App mit Prisma. Sie schreiben eine einfache Query, um Benutzer basierend auf ihrer angegebenen E-Mail und ihrem Passwort zu authentifizieren:

1const user = await prisma.user.findFirst({
2  where: { email, password },
3});

Sieht harmlos aus, oder? Aber was, wenn ein Angreifer sendet password = { "not": "" }? Anstatt das User-Objekt nur zurückzugeben, wenn E-Mail und Passwort übereinstimmen, gibt die Abfrage den User immer zurück, wenn nur die angegebene E-Mail übereinstimmt.

Diese Schwachstelle ist als Operator Injection bekannt, wird aber häufiger als NoSQL Injection bezeichnet. Was viele Entwickelnde nicht wissen, ist, dass trotz strenger Modellschemata einige ORMs anfällig für Operator Injection sind, selbst wenn sie mit einer relationalen Datenbank wie PostgreSQL verwendet werden, was es zu einem weiter verbreiteten Risiko macht als erwartet.

In diesem Beitrag untersuchen wir, wie Operator-Injection funktioniert, demonstrieren Exploits in Prisma ORM und erörtern, wie man diese verhindern kann.

Verständnis von Operator Injection

Um Operator Injection in ORMs zu verstehen, ist es interessant, sich zunächst NoSQL Injection anzusehen. MongoDB führte Entwickelnde in eine API zum Abfragen von Daten mittels Operatoren wie $eq, $lt und $neWenn Benutzereingaben blind an die Abfragefunktionen von MongoDB übergeben werden, besteht das Risiko einer NoSQL-Injection.

Gängige ORM-Bibliotheken für JavaScript begannen, eine ähnliche API für die Abfrage von Daten anzubieten, und heute unterstützen fast alle großen ORMs eine Variante von Abfrageoperatoren, selbst wenn sie MongoDB nicht unterstützen. Prisma, Sequelize und TypeORM haben alle Unterstützung für Abfrageoperatoren für relationale Datenbanken wie PostgreSQL implementiert.

Ausnutzung von Operator Injection in Prisma

Prisma-Abfragefunktionen, die auf mehr als einem Datensatz operieren, unterstützen typischerweise Abfrageoperatoren und sind anfällig für Injection-Angriffe. Beispielfunktionen sind findFirst, findMany, updateMany und deleteManyWährend Prisma die in der Abfrage referenzierten Modellfelder zur Laufzeit validiert, sind Operatoren eine gültige Eingabe für diese Funktionen und werden daher nicht von der Validierung abgelehnt.

Ein Grund, warum Operator Injection in Prisma leicht auszunutzen ist, sind die String-basierten Operatoren, die von der Prisma API angeboten werden. Einige ORM-Bibliotheken haben die Unterstützung für String-basierte Abfrageoperatoren entfernt, da diese von Entwickelnden so leicht übersehen und ausgenutzt werden können. Stattdessen zwingen sie Entwickelnde dazu, benutzerdefinierte Objekte für Operatoren zu referenzieren. Da diese Objekte nicht ohne Weiteres aus Benutzereingaben deserialisiert werden können, ist das Risiko der Operation Injection in diesen Bibliotheken stark reduziert.

Nicht alle Abfragefunktionen in Prisma sind anfällig für Operator-Injection. Funktionen, die einen einzelnen Datenbankeintrag auswählen oder ändern, unterstützen typischerweise keine Operatoren und lösen einen Laufzeitfehler aus, wenn ein Objekt bereitgestellt wird. Abgesehen von findUnique akzeptieren die Prisma-Funktionen update, delete und upsert ebenfalls keine Operatoren in ihrem where-Filter.

1  // This query throws a runtime error:
2  // Argument `email`: Invalid value provided. Expected String, provided Object.
3  const user = await prisma.user.findUnique({
4    where: { email: { not: "" } },
5  });

Best Practices zur Vermeidung von Operator Injection

1. Benutzereingaben in primitive Datentypen casten

Typischerweise genügt das Casting von Eingaben in primitive Datentypen wie Strings oder Zahlen, um Angreifer am Einschleusen von Objekten zu hindern. Im ursprünglichen Beispiel würde das Casting wie folgt aussehen:

1  const user = await prisma.user.findFirst({
2    where: { email: email.toString(), password: password.toString() },
3  });

2. Benutzereingaben validieren

Obwohl Casting effektiv ist, sollten Sie möglicherweise die Benutzereingabe validieren, um sicherzustellen, dass die Eingabe Ihren Geschäftsanforderungen entspricht.

Es gibt viele Bibliotheken für die serverseitige Validierung von Benutzereingaben, wie class-validator, zod und joi. Wenn Sie für ein Webanwendungs-Framework wie NestJS oder NextJS entwickeln, werden wahrscheinlich spezifische Methoden zur Validierung von Benutzereingaben im Controller empfohlen.

Im ursprünglichen Beispiel könnte die Zod-Validierung wie folgt aussehen:

1import { z } from "zod";
2
3const authInputSchema = z.object({
4  email: z.string().email(),
5  password: z.string().min(8)
6});
7
8const { email, password } = authInputSchema.parse({email: req.params.email, password: req.params.password});
9
10const user = await prisma.user.findFirst({
11  where: { email, password },
12});

3. Halten Sie Ihr ORM aktuell

Bleiben Sie auf dem neuesten Stand, um von Sicherheitsverbesserungen und -korrekturen zu profitieren. Zum Beispiel hat Sequelize ab Version 4.12 String-Aliase für Abfrageoperatoren deaktiviert, was die Anfälligkeit für Operator-Injection erheblich reduziert.

Fazit

Operator Injection ist eine reale Bedrohung für Anwendungen, die moderne ORMs verwenden. Die Schwachstelle resultiert aus dem Design der ORM-API und hängt nicht vom verwendeten Datenbanktyp ab. Tatsächlich kann selbst Prisma in Kombination mit PostgreSQL anfällig für Operator Injection sein. Obwohl Prisma einen gewissen integrierten Schutz gegen Operator Injection bietet, müssen Entwickelnde dennoch Eingabevalidierung und -bereinigung praktizieren, um die Anwendungssicherheit zu gewährleisten.

Anhang: Prisma-Schema für das User-Modell

1// This is your Prisma schema file,
2// learn more about it in the docs: https://pris.ly/d/prisma-schema
3
4generator client {
5  provider = "prisma-client-js"
6}
7
8datasource db {
9  provider = "postgresql"
10  url      = env("DATABASE_URL")
11}
12
13// ...
14
15model User {
16  id       Int      @id @default(autoincrement())
17  email    String   @unique
18  password String
19  name     String?
20  posts    Post[]
21  profile  Profile?
22}
4.7/5

Sichern Sie Ihre Software jetzt.

Kostenlos starten
Ohne Kreditkarte
Demo buchen
Ihre Daten werden nicht weitergegeben · Nur Lesezugriff · Keine Kreditkarte erforderlich

Werden Sie jetzt sicher.

Sichern Sie Ihren Code, Ihre Cloud und Ihre Laufzeit in einem zentralen System.
Finden und beheben Sie Schwachstellen schnell und automatisch.

Keine Kreditkarte erforderlich | Scan-Ergebnisse in 32 Sek.