Aikido

Prisma et PostgreSQL vulnérables à l'injection NoSQL ? Un risque de sécurité surprenant expliqué

|
#

Introduction

Imaginez que vous construisez une application web de blog en utilisant Prisma. Vous écrivez une simple requête pour authentifier les utilisateurs en fonction de leur adresse e-mail et de leur mot de passe fournis :

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

Semble inoffensif, n'est-ce pas ? Mais que se passe-t-il si un attaquant envoie password = { "not": "" }? Au lieu de retourner l'objet Utilisateur uniquement lorsque l'e-mail et le mot de passe correspondent, la requête retourne toujours l'Utilisateur lorsque seul l'e-mail fourni correspond.

Cette vulnérabilité est connue sous le nom d'injection d'opérateur, mais elle est plus communément appelée injection NoSQL. Ce que beaucoup de développeurs ne réalisent pas, c'est que malgré des schémas de modèle stricts, certains ORM sont vulnérables à l'injection d'opérateur même lorsqu'ils sont utilisés avec une base de données relationnelle telle que PostgreSQL, ce qui en fait un risque plus répandu que prévu.

Dans cet article, nous explorerons le fonctionnement de l'injection d'opérateurs, démontrerons des exploits dans Prisma ORM et discuterons des moyens de les prévenir.

Comprendre l'Injection d'Opérateur

Pour comprendre l'injection d'opérateurs dans les ORM, il est intéressant de se pencher d'abord sur l'injection NoSQL. MongoDB a introduit auprès des développeurs une API pour interroger les données en utilisant des opérateurs tels que $eq, $lt et $neLorsque les entrées utilisateur sont transmises aveuglément aux fonctions de requête de MongoDB, il existe un risque d'injection NoSQL.

Les bibliothèques ORM populaires pour JavaScript ont commencé à offrir une API similaire pour l'interrogation de données, et désormais presque tous les ORM majeurs prennent en charge une certaine variation d'opérateurs de requête, même s'ils ne supportent pas MongoDB. Prisma, Sequelize et TypeORM ont tous implémenté le support des opérateurs de requête pour les bases de données relationnelles telles que PostgreSQL.

Exploitation de l'injection d'opérateurs dans Prisma

Les fonctions de requête Prisma qui opèrent sur plus d'un enregistrement prennent généralement en charge les opérateurs de requête et sont vulnérables à l'injection. Des exemples de fonctions incluent findFirst, findMany, updateMany et deleteManyBien que Prisma valide les champs de modèle référencés dans la requête en temps d'exécution, les opérateurs sont une entrée valide pour ces fonctions et ne sont donc pas rejetés par la validation.

L'une des raisons pour lesquelles l'injection d'opérateurs est facile à exploiter dans Prisma réside dans les opérateurs basés sur des chaînes de caractères offerts par l'API Prisma. Certaines bibliothèques ORM ont supprimé le support des opérateurs de requête basés sur des chaînes de caractères, car ils sont si facilement négligés par les développeurs et faciles à exploiter. Au lieu de cela, elles obligent les développeurs à référencer des objets personnalisés pour les opérateurs. Comme ces objets ne peuvent pas être facilement désérialisés à partir de l'entrée utilisateur, le risque d'injection d'opérations est considérablement réduit dans ces bibliothèques.

Toutes les fonctions de requête dans Prisma ne sont pas vulnérables à l'injection d'opérateurs. Les fonctions qui sélectionnent ou modifient un seul enregistrement de base de données ne prennent généralement pas en charge les opérateurs et génèrent une erreur d'exécution lorsqu'un objet est fourni. Outre findUnique, les fonctions Prisma update, delete et upsert n'acceptent pas non plus les opérateurs dans leur filtre 'where'.

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  });

Bonnes pratiques pour prévenir l'injection d'opérateurs

1. Convertir les entrées utilisateur en types de données primitifs

Généralement, le transtypage des entrées vers des types de données primitifs tels que les chaînes de caractères ou les nombres suffit à empêcher les attaquants d'injecter des objets. Dans l'exemple original, le transtypage se présenterait comme suit :

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

2. Valider les entrées utilisateur

Bien que le casting soit efficace, vous pourriez vouloir valider l'entrée utilisateur pour vous assurer qu'elle répond aux exigences de votre logique métier.

Il existe de nombreuses bibliothèques pour la validation côté serveur des entrées utilisateur, telles que class-validator, zod et joi. Si vous développez pour un framework d'application web comme NestJS ou NextJS, ils recommandent probablement des méthodes spécifiques pour la validation des entrées utilisateur dans le contrôleur.

Dans l'exemple original, la validation Zod pourrait se présenter comme suit :

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. Maintenez votre ORM à jour

Restez à jour pour bénéficier des améliorations et correctifs de sécurité. Par exemple, Sequelize a désactivé les alias de chaîne pour les opérateurs de requête à partir de la version 4.12, ce qui réduit considérablement la susceptibilité à l'injection d'opérateurs.

Conclusion

L'injection d'opérateurs est une menace réelle pour les applications utilisant des ORM modernes. La vulnérabilité découle de la conception de l'API ORM et n'est pas liée au type de base de données utilisé. En effet, même Prisma combiné à PostgreSQL peut être vulnérable à l'injection d'opérateurs. Bien que Prisma offre une certaine protection intégrée contre l'injection d'opérateurs, les développeurs doivent toujours pratiquer la validation et la désinfection des entrées pour assurer la sécurité des applications.

Annexe : Schéma Prisma pour le modèle utilisateur

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

Sécurisez votre logiciel dès maintenant.

Essai gratuit
Sans CB
Planifiez une démo
Vos données ne seront pas partagées - Accès en lecture seule - Pas de CB nécessaire

Sécurisez-vous maintenant.

Sécuriser votre code, votre cloud et votre runtime dans un système centralisé unique.
Détectez et corrigez les vulnérabilités rapidement et automatiquement.

Pas de carte de crédit requise | Résultats du scan en 32 secondes.