Aller au contenu

Injection NoSQL

L'injection NoSQL suit le même esprit que l'injection SQL, transposé aux bases non relationnelles. On distingue deux types : l'injection de syntaxe, où l'on sort de la syntaxe de la requête pour injecter son propre payload, et l'injection d'opérateur, où l'on détourne les opérateurs de requête de la base pour en manipuler la logique. Les bases NoSQL employant des langages et structures variés, la nature des attaques varie ; la plus répandue, et le fil conducteur de cette section, est MongoDB.

Injection de syntaxe

On détecte la faille en cherchant à provoquer une erreur de syntaxe, à l'aide de chaînes de fuzzing et de caractères spéciaux soumis à la place des paramètres. Une chaîne de fuzzing typique combine plusieurs métacaractères (à encoder en URL si l'injection s'y trouve) :

'"`{
;$Foo}
$Foo \xYZ

Si la réponse change, l'entrée est probablement mal assainie. Pour identifier quels caractères sont interprétés comme de la syntaxe, on les injecte un par un. Une apostrophe seule qui modifie la réponse suggère une erreur de syntaxe ; on confirme en envoyant une version échappée valide qui, elle, ne doit plus produire d'erreur :

this.category == '''      → modifie la réponse (erreur)
this.category == '\''      → réponse normale (confirmation)

Comportement conditionnel. Une fois la faille détectée, on vérifie qu'on peut influencer une condition booléenne en envoyant une condition fausse puis une condition vraie, et en comparant les réponses :

fizzy' && 0 && 'x      → condition fausse
fizzy' && 1 && 'x      → condition vraie

Surcharge des conditions. On injecte alors une condition toujours vraie pour renvoyer tous les enregistrements, y compris les éléments masqués :

fizzy'||'1'=='1

donnant côté MongoDB this.category == 'fizzy'||'1'=='1'.

Risque de perte de données

Une condition toujours vraie est dangereuse : certaines applications réutilisent une même requête pour plusieurs opérations, ce qui peut entraîner une perte de données accidentelle (suppression ou mise à jour massive).

Caractère nul. MongoDB peut ignorer tout ce qui suit un caractère nul, ce qui permet de neutraliser la fin d'une requête :

fizzy'%00      → tronque this.category == 'fizzy' && this.released == 1

Injection d'opérateur

Les bases NoSQL utilisent des opérateurs de requête pour exprimer des conditions. Quelques-uns parmi les plus utiles côté attaque : $where (satisfait une expression JavaScript), $ne (différent de), $in (parmi un tableau), $regex (correspond à une expression régulière). La syntaxe JSON est {"username":{"$ne":"invalid"}}, l'équivalent en URL username[$ne]=invalid.

Si l'injection via l'URL échoue, on convertit la requête de GET en POST, on passe le Content-Type en application/json, et on injecte les opérateurs dans le corps JSON (l'extension Content Type Converter de Burp automatise cette conversion). Sur une requête d'authentification, on procède par étapes — détecter, puis construire une attaque générique ou ciblée :

{"username":{"$ne":"invalid"},"password":{"$ne":"invalid"}}
{"username":{"$in":["admin","administrator","superadmin"]},"password":{"$ne":""}}
{"username":{"$regex":".*admin.*"},"password":{"$ne":""}}

Extraire des données par injection de syntaxe

Certains opérateurs et fonctions exécutent du JavaScript limité — c'est le cas de l'opérateur $where et de mapReduce() de MongoDB. Une application vulnérable exécute alors ce JavaScript, ce qui permet d'extraire des données. Pour une recherche d'utilisateur produisant {"$where":"this.username == 'admin'"}, on injecte une condition qui teste le mot de passe caractère par caractère :

admin' && this.password[0] == 'a' || 'a'=='b

La fonction match() permet des tests plus fins, par exemple savoir si le mot de passe contient un chiffre :

admin' && this.password.match(/\d/) || 'a'=='b

Identifier les champs. MongoDB gérant des données semi-structurées sans schéma fixe, il faut souvent découvrir les champs valides avant d'extraire. On teste l'existence d'un champ en comparant la réponse à celle d'un champ connu et à celle d'un champ inexistant :

admin' && this.username!='     → réponse identique au champ connu
admin' && this.foo!='          → réponse différente (champ absent)

La méthode keys() permet d'extraire les noms de champs caractère par caractère :

"$where":"Object.keys(this)[0].match('^.{0}a.*')"

Extraire des données par injection d'opérateur

Même si la requête d'origine n'exécute pas de JavaScript, on peut parfois injecter un opérateur. On le confirme par des conditions booléennes — $where:"0" contre $where:"1" — en observant si la réponse varie. On enchaîne ensuite extraction de champs (via Object.keys) et extraction de valeurs. Quand $where n'est pas disponible, $regex permet d'extraire une valeur caractère par caractère :

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

En combinant les deux, on localise un champ puis on en lit la valeur, par exemple un jeton de réinitialisation que l'on exploite ensuite sur l'endpoint correspondant :

{"username":"victime","password":{"$ne":"invalid"},"$where":"this.unlockToken[0]=='a'"}

Injection temporelle

Quand l'erreur ne produit aucun résultat visible, on conditionne un délai à la véracité d'une condition. On établit d'abord un temps de réponse de référence, puis on injecte un payload temporel et on observe le ralentissement :

admin'+function(x){if(x.password[0]==="a"){sleep(5000)};}(this)+'

Un délai confirme que le premier caractère du mot de passe est a.

Aide-mémoire

Objectif Approche
Détecter (syntaxe) Chaîne de fuzzing, puis caractères un par un
Condition toujours vraie '||'1'=='1, ou caractère nul %00
Détecter (opérateur) {"$ne":...} en JSON ; convertir GET → POST si besoin
Contourner l'authentification $ne, $in, $regex sur les champs
Extraire (JavaScript) $where + test caractère par caractère, Object.keys
Extraire (sans JavaScript) $regex caractère par caractère
Aucune réponse visible Injection temporelle (sleep)

L'extension Content Type Converter de Burp facilite le passage en JSON nécessaire à l'injection d'opérateur, étape souvent décisive pour exploiter une authentification MongoDB.