Mongoose est une bibliothèque de modélisation des données d'objets (ODM) pour MongoDB qui simplifie les interactions avec les bases de données dans les applications Node.js. En fournissant une solution basée sur un schéma, Mongoose permet aux objets JavaScript d'être mappés vers des documents MongoDB, agissant comme une couche d'abstraction qui aide à structurer les données pour une gestion et une validation plus faciles. Grâce à des fonctionnalités telles que l'intergiciel pour l'exécution de logiques personnalisées et un système intuitif de construction de requêtes, Mongoose améliore l'efficacité du travail avec MongoDB. Mongoose, décrit comme "l'élégant modèle d'objet MongoDB pour Node.js", a recueilli 27 000 étoiles sur GitHub, ce qui témoigne de son utilisation répandue et de son appréciation par les développeurs.
Programme de bourses OPSWAT et découverte des vulnérabilités critiques
Le programmeOPSWAT Critical Infrastructure Cybersecurity Graduate Fellowship Program, basé au Viêt Nam, offre aux étudiants de troisième cycle une expérience pratique de la sécurisation des infrastructures critiques. Dans le cadre de ce programme, les boursiers ont la possibilité d'analyser et de traiter les vulnérabilités en matière de cybersécurité, en collaborant avec les experts d'OPSWAT pour relever des défis concrets dans des domaines tels que la détection des logiciels malveillants, la sécurité des fichiers et la prévention des menaces.
Au cours du programme de bourses OPSWAT , les participants étudient et reproduisent systématiquement les CVE connus dans divers produits, bibliothèques et systèmes d'exploitation. Dans le cadre de cette initiative, Dat Phung - l'un de nos boursiers distingués - a choisi d'examiner Mongoose en raison de son adoption généralisée dans les environnements de production. En novembre 2024, il a découvert une vulnérabilité critique dans Mongoose lors d'une analyse approfondie de la bibliothèque. Cette vulnérabilité permettait à un attaquant d'exploiter la valeur $where, ce qui pouvait conduire à une exécution de code à distance (RCE) sur le serveur d'application Node.js. Après avoir rapidement signalé le problème à Mongoose, un correctif a été publié dans le cadre de la version 8.8.3, et la CVE-2024-53900 a été divulguée dans la base de données nationale des vulnérabilités (National Vulnerability Database, NVD).
CVE-2024-53900 & CVE-2025-23061 Chronologie
- 7 novembre 2024 : Dat Phung identifie une vulnérabilité critique dans Mongoose et soumet un rapport de sécurité à Snyk.
- 26 novembre 2024 : Mongoose a publié la version 8.8.3 pour corriger cette vulnérabilité.
- Le 2 décembre 2024 : La base de données nationale des vulnérabilités (NVD) a divulgué CVE-2024-53900 pour cette vulnérabilité.
- 17 décembre 2024 : En analysant le correctif 8.8.3 de Mongoose, Dat Phung a trouvé un contournement qui permettait encore l'exécution de code à distance (RCE). Un rapport de sécurité détaillé a été soumis à Tidelift.
- 13 janvier 2025 : Mongoose publie la version 8.9.5, introduisant un correctif amélioré qui corrige efficacement le contournement.
- 15 janvier 2025 : La base de données nationale sur les vulnérabilités (National Vulnerability Database, NVD) divulgue officiellement le CVE-2025-23061, soulignant la gravité de la vulnérabilité nouvellement identifiée.
Méthode Populate() de Mongoose
Mongoose fournit également une fonctionnalité utile appelée populate() qui améliore la capacité à travailler avec des relations entre les documents. Alors que les versions ≥ 3.2 de MongoDB disposent de l'opérateur d'agrégation $lookup pour les jointures, la fonction populate() de Mongoose offre une alternative plus puissante pour remplacer automatiquement une référence par les données correspondantes des documents apparentés. Ceci est particulièrement utile pour gérer les relations entre différentes collections MongoDB, comme lorsqu'un document fait référence à un autre par son _id. [2]
Lors de la définition d'un schéma dans Mongoose, un champ peut être défini pour référencer un autre modèle à l'aide de l'option ref. La méthode populate() est alors utilisée pour remplacer le champ référencé (un ObjectId) par le document complet du modèle associé. Par exemple, dans une application de librairie, le champ author dans le bookSchema fait référence au document Author, et le champ review fait référence au document Reviews. La méthode populate() permet aux développeurs de remplacer le champ auteur (qui est un ObjectId) par le document complet Author lorsqu'ils interrogent le modèle book.
La fonction populate() permet aux développeurs de remplacer le champ auteur (qui est un ObjectId) par le document Author complet lorsqu'ils interrogent le modèle de livre:
En outre, la méthode populate() de Mongoose prend en charge les requêtes personnalisées pour définir quels documents connexes sont récupérés et comment ils le sont. Des propriétés telles que match et options permettent aux développeurs de filtrer, trier, limiter et sauter des documents connexes, offrant ainsi des capacités flexibles de récupération de données.
Analyse CVE-2024-53900
Dans le cadre du programme OPSWAT Cybersecurity Graduate Fellowship, Dat Phung a analysé Mongoose pour reproduire des CVE connus et a procédé à un examen complet du fonctionnement interne de la méthode populate(), qui joue un rôle clé dans la gestion des relations entre les documents MongoDB. La méthode populate() prend en charge les arguments de type chaîne et objet, et les développeurs peuvent utiliser l'option match pour appliquer des filtres spécifiques aux données récupérées :
Dans l'exemple ci-dessus, l'option match est un objet filtre qui peut inclure des opérateurs de requête MongoDB, comme détaillé dans Query and Projection Operators - MongoDB Manual v8.0. Un opérateur notable est $where, qui permet l'exécution de JavaScript directement sur le serveur MongoDB. Toutefois, cette exécution sur le serveur MongoDB est restreinte et ne prend en charge que les opérations et fonctions de base.
Dat Phung a effectué une analyse approfondie du code source de Mongoose pour comprendre le déroulement des opérations de la méthode populate(). Il a déterminé qu'après que l'application ait appelé la méthode populate() sur le modèle, la fonction populate() est déclenchée. Dans cette fonction, Mongoose appelle la fonction _execPopulateQuery() , qui exécute la requête avec l'opérateur $where sur le serveur MongoDB. Par la suite, tous les documents de la collection étrangère sont récupérés pour être peuplés dans les étapes suivantes.
Après avoir récupéré les données de MongoDB, Mongoose exécute la fonction de rappel _done(), qui appelle _assign() pour préparer les données avant de "joindre" les deux modèles en appelant la fonction assignVals().
La vulnérabilité peut survenir lorsque les données récupérées sont traitées par la fonction assignVals() de Mongoose. Cette fonction vérifie si l'option match est un tableau et, si c'est le cas, transmet chaque opérateur à la fonction sift(). La fonction sift() , qui est importée d'une bibliothèque externe du même nom, traite ces requêtes localement sur le serveur d'application. Ce traitement local présente un risque de sécurité, en particulier lorsqu'il s'agit de manipuler des données contrôlées par l'utilisateur.
Pour approfondir cette question, Dat Phung a modifié les valeurs de l'option match afin de s'assurer que les conditions étaient remplies, invoquant ainsi la fonction sift() pour une analyse supplémentaire du flux de données.
Une fois la condition mise en place, l'opérateur $where a été transmis à la fonction sift().
La bibliothèque sift est un utilitaire JavaScript léger conçu pour filtrer et interroger des collections de données telles que des tableaux ou des objets JSON à l'aide d'une syntaxe similaire à celle de MongoDB. Selon la documentation officielle, "Sift est une petite bibliothèque permettant d'utiliser les requêtes MongoDB en JavaScript". La fonction sift() évalue les opérations de filtrage de type MongoDB sur le serveur d'application au lieu du serveur de base de données, ce qui peut exposer le système à des risques de sécurité importants lors du traitement d'entrées non fiables.
Poursuivant son analyse, notre collaborateur a identifié un problème dans la fonction createDefaultQueryTester() de la bibliothèque sift. Cette fonction convertit chaque opération du tableau de correspondance en fonctions JavaScript exécutables, qui sont ensuite utilisées pour filtrer et traiter localement les données des documents MongoDB. Pour ce faire, createDefaultQueryTester() invoque la fonction createNamedOperation(), en passant des opérations telles que $where du tableau de correspondance comme arguments.
Pour chaque opération du tableau de correspondance, createNamedOperation vérifie si l'opération est prise en charge et la transmet à la fonction correspondante.
Si l'opération est $where, une fonction JavaScript est générée en utilisant la valeur brute "params", qui est dérivée de l'opérateur $where dans le tableau de correspondance et peut être contrôlée par l'utilisateur.
CVE-2024-53900 : Détails de l'exploitation
Alors que MongoDB limite l'exécution des fonctions JavaScript via l'opération $where, comme nous l'avons analysé précédemment, la fonction sift() permet l'exécution de ces fonctions sans ces restrictions. Cette absence de validation et de restriction des entrées introduit une importante vulnérabilité en matière de sécurité, car la valeur "params" - directement contrôlée par l'entrée de l'utilisateur - peut être exploitée, ce qui peut conduire à des attaques par injection de code. Pour examiner ce problème plus en détail, Dat Phung a construit la requête suivante :
Initialement, la requête a échoué lors de l'exécution d'un autre processus, ce qui a entraîné l'erreur suivante :
Cette erreur indique que Mongoose tente d'exécuter l'opération $where sur le serveur MongoDB avant de passer le contrôle à la fonction sift(). Cependant, en raison des restrictions sur les fonctions JavaScript dans la clause $where de MongoDB, une erreur se produit, empêchant l'exécution de la requête. Par conséquent, Mongoose interrompt le processus avant d'atteindre la fonction sift() .
Pour contourner cette limitation, notre étudiant a utilisé la variable "global" présente sur le serveur d'application, qui n'existe pas sur le serveur MongoDB. Cette approche lui a permis de contourner la restriction sur le serveur MongoDB et de permettre à la requête d'atteindre la fonction sift():
Avec cette valeur, lorsque Mongoose exécute l'opération $where sur MongoDB, l'absence de la variable "global" fait que l'opérateur ternaire (typeof global != "undefined" ?global.process.mainModule.constructor._load("child_process").exec("calc") : 1) de renvoyer 1, ce qui empêche MongoDB d'envoyer une erreur. Par conséquent, la requête est exécutée sur le serveur MongoDB sans problème.
Cependant, lorsque la même valeur atteint la fonction sift(), qui s'exécute sur le serveur d'application où la variable "global" est disponible, elle déclenche la création de la fonction suivante :
Preuve de concept de l'exécution de code à distance (RCE)
Dans l'exemple d'application fourni au début de ce blog, si un attaquant envoyait la requête suivante, il pourrait exécuter avec succès une attaque par exécution de code à distance (RCE) :
La vidéo démontre la preuve de concept pour la CVE-2024-53900 affectant les versions de Mongoose antérieures à 8.8.3, qui manque de validation d'entrée appropriée pour empêcher l'utilisation abusive de l'opérateur $where avec la bibliothèque sift.
Correction incomplète et CVE-2025-23061
Sur la base du rapport de sécurité de Dat Phung, Mongoose a introduit un correctif visant à résoudre la vulnérabilité précédemment identifiée (CVE-2024-53900) avant sa divulgation publique. Le correctif en question(Automattic/mongoose@33679bc) a ajouté une vérification pour interdire l'utilisation de $where dans la propriété match passée à populate().
Cet extrait vérifie si la propriété match passée dans populate() est un tableau. Si c'est le cas, le code parcourt chaque objet du tableau pour voir s'il contient l'opérateur $where. Si $where est détecté, une erreur est levée, empêchant la charge utile malveillante de se propager jusqu'à la fonction risquée sift().
En conséquence, la charge utile exploitant CVE-2024-53900 échoue cette vérification parce qu'un objet dans le tableau de correspondance contient $where, ce qui l'empêche d'atteindre sift().
Bien que cette mise à jour bloque correctement l'utilisation directe de $where à l'intérieur d'un seul niveau d'imbrication, elle ne détecte pas $where lorsqu'il est intégré dans un opérateur $or - une structure que MongoDB et la bibliothèque sift supportent entièrement.
Par conséquent, un attaquant peut imbriquer $where sous $or pour échapper à la vérification à un seul niveau du correctif. Comme Mongoose n'inspecte que les propriétés de premier niveau de chaque objet dans le tableau de correspondance, la charge utile du contournement n'est pas détectée et finit par atteindre la bibliothèque sift, ce qui permet le RCE malveillant.
Preuve de concept pour CVE-2025-23061
Pour illustrer la nature incomplète de la correction, Dat Phung a reconstruit l'application d'exemple en utilisant une version de Mongoose 8.9.4 (postérieure à 8.8.3). En imbriquant $whereà l'intérieur d'une clause $or, un attaquant peut contourner la vérification et obtenir un RCE.
L'exploitation de la preuve de concept démontre comment CVE-2025-23061 peut être déclenchée dans les versions de Mongoose antérieures à 8.9.5, permettant à un attaquant d'exécuter un code arbitraire sur le serveur :
Atténuation et orientation
Pour atténuer les vulnérabilités évoquées ci-dessus, assurez-vous que votre système est mis à jour avec la dernière version de Mongoose.
MetaDefender Core utilisant le moteur SBOM peut détecter cette vulnérabilité
OPSWAT MetaDefender CoreMetaDefender Core, équipé de fonctionnalités SBOMSoftware Bill of Materials) avancées, permet aux organisations d'adopter une approche proactive dans la gestion des risques de sécurité. En analysant les applications logicielles et leurs dépendances, MetaDefender Core identifie les vulnérabilités connues, telles que CVE-2024-53900 et CVE-2025-23061, dans les composants listés. Cela permet aux équipes de développement et de sécurité de donner la priorité aux correctifs, en atténuant les risques de sécurité potentiels avant qu'ils ne soient exploités par des acteurs malveillants.
Voici une capture d'écran des CVE-2024-53900 et CVE-2025-23061, qui ont été détectés par MetaDefender Core avec SBOM :
En outre, les CVE peuvent également être détectés par MetaDefender Software Supply Chainqui s'appuie sur MetaDefender Core et SBOM pour identifier ces vulnérabilités.