Une plateforme d'apprentissage qui permet aux étudiants de composer des requêtes MongoDB a abandonné le module vm de Node.js et analyse désormais chaque requête sous forme d'arbre de syntaxe abstraite (AST) avant son exécution. Ce changement supprime un bac à sable qui pouvait être contourné, protégeant ainsi le backend contre tout code arbitraire qu'une chaîne malformée pourrait injecter.
Pourquoi le bac à sable original a échoué
La première implémentation enveloppait un handle de base de données actif dans un appel vm.runInContext et tentait de restreindre les utilisateurs à l'aide d'une expression régulière. Elle n'autorisait que des noms de méthodes tels que find ou aggregate ; tous les autres identifiants étaient censés être filtrés.
Deux failles rendaient cette approche vulnérable :
- Le filtrage par regex peut être contourné. JavaScript permet au code d'utiliser la notation entre crochets (
obj["constructor"]) pour accéder à n'importe quelle propriété. Un attaquant peut récupérer le constructeurFunction, construire une nouvelle fonction et exécuter le code de son choix. La regex ne voit jamais l'accès à la propriété sous-jacente car la source peut être réécrite de multiples façons. vmn'est pas une barrière de sécurité. La documentation de Node stipule quevmisole l'objet global mais pas l'ensemble du processus. En injectant une connexion de base de données active dans le bac à sable, le code à l'intérieur du contexte conservait la capacité d'appeler n'importe quelle méthode sur cette connexion, y compris celles qui écrivent ou suppriment des données. Le bac à sable n'empêchait pas le code d'affecter le processus hôte.
La solution basée sur l'AST
L'équipe a remplacé l'exécution de code par une analyse statique. Les chaînes de requêtes alimentent désormais le parseur acorn, qui produit un AST — une représentation arborescente de la structure syntaxique du code. L'AST est examiné nœud par nœud par rapport à une liste blanche stricte :
- Les littéraux, les tableaux et les objets ne sont autorisés que lorsqu'ils apparaissent comme des valeurs simples.
- Les appels de méthodes sont limités à un ensemble prédéfini (
find,sort,limit, etc.). Tout autre appel est rejeté. - L'accès à une propriété calculée (par exemple,
obj[expr]) ou tout autre type de nœud non explicitement listé déclenche un échec immédiat.
Comme le parseur travaille sur l'arbre et non sur le texte brut, il ne peut pas être trompé par des orthographes alternatives ou des astuces de notation entre crochets. Une chaîne de constructeurs qui aurait pu passer entre les mailles de la regex apparaît comme un nœud non reconnu et est rejetée avant l'exécution de tout code.
Ce que cela signifie pour la sécurité
La nouvelle conception suit une philosophie de « refus par défaut » :
- Définissez ce qui est autorisé, pas ce qui est interdit. L'établissement d'une liste blanche textuelle ne peut être exhaustif ; un AST possède un ensemble fini de types de nœuds, ce qui rend une vérification exhaustive réalisable.
- N'exposez jamais de ressources actives à l'intérieur d'un bac à sable. Passer un handle de base de données dans un contexte isolé donne au code du bac à sable une ligne directe vers le backend. L'approche par parseur ne transmet jamais d'objet actif au code utilisateur ; elle extrait uniquement l'intention de la requête.
- Validez la structure, puis exécutez en toute sécurité. Une fois que l'AST a passé la validation, la plateforme traduit les appels autorisés en méthodes réelles du driver MongoDB en utilisant son propre chemin de code de confiance.
Ce qu'il faut surveiller ensuite
- Auditez toute utilisation de
eval,new Functionouvmdans votre base de code. Même une liste blanche peut être subvertie par la nature dynamique de JavaScript. - Adoptez le parsing AST pour le code généré par les utilisateurs dès que possible. Des bibliothèques comme
acorn,esprimaoubabel-parserrendent la transformation simple. - Limitez l'exposition des objets actifs. Si une connexion de base de données, un handle de fichier ou un socket réseau doit être accessible, enveloppez-le dans un proxy qui n'expose que les méthodes minimales que vous avez l'intention d'autoriser.
- Automatisez les tests de cas limites. Générez des requêtes utilisant la notation entre crochets, des clés calculées ou la manipulation de prototypes pour vérifier que votre parseur les rejette.
À retenir
Se fier à vm.runInContext comme bac à sable donne un faux sentiment de sécurité ; les filtres regex ne peuvent pas couvrir la syntaxe flexible de JavaScript, et le bac à sable n'isole pas les ressources du processus. L'analyse de l'entrée utilisateur en un AST et la mise en liste blanche des seuls nœuds que vous comprenez fournit une barrière concrète et maintenable qui arrête le code malveillant avant son exécution. Si votre plateforme permet aux utilisateurs d'écrire du code, remplacez l'exécution de type eval par une analyse statique dès aujourd'hui.
