Les injections NoSQL exploitent des défauts de développement au niveau des requêtes réalisées sur les bases de données dites « NoSQL » (MongoDB, Cassandra, Neo4j, etc.). Ces bases de données s’éloignent du modèle relationnel classique. Ainsi, une nouvelle manière de réaliser des requêtes a été développée.
En particulier, MongoDB, qui stocke les informations sous forme de documents, utilise un format basé sur le standard JSON comme structure de requête.
Introduction aux injections MongoDB
Une requête SQL permettant de récupérer les informations d’un utilisateur est de la forme :
Pour MongoDB, cette requête s’écrit de la manière suivante :
Cette différence de langage ne permet donc pas d’exploiter les injections SQL traditionnelles. Cependant, des injections inhérentes à cette syntaxe peuvent être exploitées.
Deux types d’exploitations sont possibles :
- Injection d’opérateur : dans le cas d’un défaut de validation des entrées utilisateur sur une requête, il est possible d’injecter des opérateurs afin d’altérer le comportement de la requête initialement prévue par le développeur
- Injection de code JavaScript : dans le cas de l’utilisation d’une entrée utilisateur au sein d’un opérateur faisant appel à une fonction JavaScript, il est possible d’injecter du code JavaScript au sein de l’environnement Mongo
Injection d’opérateur
Ce type d’injection permet d’exploiter un défaut de vérification des entrées utilisateur, en particulier du type de données fournies par l’utilisateur, au sein des requêtes Mongo. Un code présentant une telle vulnérabilité peut prendre la forme suivante :
Prenons un formulaire de réinitialisation de mot de passe qui retourne un message spécifique lorsque le nom d’utilisateur est déjà présent dans la base de données. Le code montré ci-dessus est utilisé afin de vérifier la présence ou non du nom d’utilisateur.
Ici, l’attaquant contrôle la valeur ainsi que le type de donnée de la variable username. Il peut ainsi passer un objet JSON à la place de la chaîne de caractères attendue par le développeur. Cet objet sera alors interprété par le moteur de requêtes Mongo.
Par exemple, en utilisant l’opérateur $regex, il est possible d’itérer afin de déterminer le nom d’utilisateur :
Si le serveur retourne l’erreur spécifiant qu’aucun utilisateur n’a été identifié, alors il n’y a pas d’utilisateur commençant par la lettre a. Sinon, au moins un utilisateur commence par a, et il est possible de répéter ce processus afin de déterminer le nom d’utilisateur.
Injection de code JavaScript
Une fonctionnalité de MongoDB permet d’exécuter du code JavaScript au sein d’un environnement bac à sable. Cela permet ainsi d’effectuer un traitement sur les données avant que l’application, qui a réalisé la requête, ne reçoive les données associées.
Par exemple, une simple requête utilisant le moteur JavaScript de MongoDB retournant l’utilisateur admin peut prendre la forme :
Au sein de cet environnement JavaScript, le mot-clé this fait référence au document actuellement traité. L’ensemble des champs de ce document sont ainsi exposés à travers la variable this.
Deux fonctionnalités permettent l’exécution de code JavaScript :
- les opérateurs $where, $function et $accumulator
- les fonctions group et mapReduce
Un défaut de validation des entrées utilisateur peut amener à l’exécution de code JavaScript non sûr au sein de l’environnement bac à sable.
Bien que cela ne permette pas d’interagir avec le système sous-jacent, l’exploitation de cette vulnérabilité peut néanmoins permettre d’interagir directement avec la base de données.
Exemple de code vulnérable :
Le contenu de la valeur associée à la clé username dans le JSON du corps de la requête HTTP sera inséré dans la requête MongoDB.
Le champ password ne peut pas être exploité en raison d’un traitement qui va générer un hash à partir de son contenu.
Lors de l’exécution de la requête, le moteur de la base Mongo va interpréter le code JavaScript, contenant la charge malveillante.
Injection de code JavaScript pour contourner les mécanismes d’authentification
Le code vulnérable précédent peut, par exemple, être exploité au sein d’une fonction de connexion telle que :
Il est possible de contourner le mécanisme d’authentification en place via une charge utile telle que :
Le code JavaScript exécuté sera ainsi :
Note : Les parenthèses ont été ajoutées pour des raisons de lisibilité. En effet, dans cet exemple, deux || 1===1 sont requis en raison de la précédente donnée par JavaScript sur les opérateurs booléens && et || (le premier pour neutraliser la condition sur le nom d’utilisateur, le deuxième pour neutraliser la condition sur le mot de passe. Comme pour les injections SQL, la charge utile permettant d’exploiter pleinement la vulnérabilité dépend du point d’injection).
Injection de code JavaScript pour accéder à des informations stockées dans la base
Cependant, d’autres formes d’injection sont également réalisables, et permettent de récupérer des informations stockées en base. En effet, en fonction de la réponse du serveur, il est possible de déterminer si une condition est vraie ou non. Cela est possible en exploitant la variable this qui fait référence au document actuellement traité.
Afin de récupérer des informations sur le mot de passe par exemple, il est possible d’utiliser une charge utile de la forme :
Le code JavaScript exécuté au sein de la base MongoDB sera ainsi le suivant :
En itérant sur POS (position du caractère dans le mot de passe) et CHAR (valeur de ce caractère), il est possible de déterminer le mot de passe (ou le condensat).
En effet, lorsque le caractère CHAR est trouvé à la position POS, la requête Mongo retournera un résultat. Aucun résultat ne sera retourné si le caractère ne correspond pas.
Par exemple, si la première valeur du mot de passe est un a, alors une réponse sera retournée par le serveur, sinon, aucune réponse ne sera retournée :
Cette catégorie d’injection NoSQL nécessite des prérequis sur la structure des requêtes utilisées par l’application pour contacter la base de données Mongo. En effet, l’exécution de code JavaScript est limitée aux opérateurs et fonctions listés précédemment, il est donc nécessaire de contrôler un champ dont la valeur est insérée au sein d’un de ces opérateurs ou fonctions ($where, $function, $accumulator, group ou mapReduce).
En particulier, l’opérateur $where, souvent utilisé pour faciliter le développement peut être remplacé par des opérateurs natifs de MongoDB, ce qui permet de se prémunir contre de telles attaques.
Ainsi, la requête présentée précédemment utilisant $where :
Peut être remplacée de façon sécurisée par une requête telle que :
Conclusion
Les injections NoSQL, bien que souvent méconnues, sont, au même titre que les injections SQL, particulièrement dangereuses en raison de l’exposition des données.
Afin de se prémunir contre ce type d’attaques, plusieurs vérifications peuvent être faites :
- Vérifier les entrées utilisateur, en particulier le type, afin d’empêcher un utilisateur malveillant d’injecter des objets JSON au sein de requêtes MongoDB
- Utiliser des requêtes ne faisant pas intervenir la fonctionnalité d’exécution de code JavaScript (telle que $where, $function, $accumulator, group et mapReduce) lorsque celles-ci manipulent des données provenant d’entrées utilisateur
- Désactiver le moteur JavaScript de MongoDB :
- En lançant le programme mongo avec l’option –noscripting
- En mettant la valeur du paramètre security.javascriptEnabled à false