Contournement d'injection NoSQL dans Mongoose, corrigé en 9.11.4 (CVE-2026-125120)
Mongoose applique mal sanitizeFilter : des filtres contrôlés par l'attaquant atteignent countDocuments et les connexions ; corrigé en 9.11.4.
En bref
- La défense
sanitizeFiltermanque surcountDocumentset sur les connexions, et trois chemins copient des propriétés héritées. Des filtres contrôlés par l'attaquant peuvent contourner l'autorisation, fausser des comptes ou faire planter des requêtes. - Mongoose avant 6.13.12, 7.8.13, 8.24.7 et 9.11.4 est concerné sur les quatre lignes prises en charge. La faille
countDocumentsest CVE-2026-125120, notée CVSS v3.1 9,8 Critique. - Aucune exploitation dans la nature n'est connue. Les avis contiennent du code de preuve de concept, et aucune des failles ne figure dans le catalogue Known Exploited Vulnerabilities de la CISA au 11 novembre 2026.
- Mettez à jour vers 6.13.12, 7.8.13, 8.24.7 ou 9.11.4. Si vous dépendez de
sanitizeFilter, ajoutez le middlewarecountDocumentsde l'avis jusqu'à la mise à jour complète. Voir « Comment corriger ? ».
Suis-je concerné ?
Toutes ces conditions doivent être réunies :
- Votre application utilise Mongoose et transmet des valeurs contrôlées par l'attaquant dans des filtres, des mises à jour ou la construction de documents.
- L'application s'appuie sur
sanitizeFilterpour se protéger. Cela peut être unmongoose.set('sanitizeFilter', true)global, une option de connexion ou une option par requête. - Pour la faille critique, l'application appelle
countDocumentsavec un filtre contenant des valeurs contrôlées par l'attaquant.
Les deux autres avis élargissent les conditions. La faille de connexion concerne les applications qui définissent sanitizeFilter sur une connexion créée avec mongoose.createConnection(uri, { sanitizeFilter: true }). La faille des propriétés héritées concerne les applications qui chargent des documents ou définissent des chemins pointés avec des noms de champs contrôlés par l'attaquant, même sans sanitizeFilter.
Mongoose est une bibliothèque MongoDB largement utilisée pour Node.js. Le registre npm a enregistré 8 868 838 téléchargements du paquet mongoose dans la semaine se terminant le 9 novembre 2026, et le dépôt GitHub compte 27 449 étoiles.
| Branche | Versions concernées | Version corrigée |
|---|---|---|
| 9.x | 9.0.0 à 9.11.3 | 9.11.4, publiée le 2026-10-23 |
| 8.x | 8.0.0 à 8.24.6 | 8.24.7, publiée le 2026-10-23 |
| 7.x | 7.0.0 à 7.8.12 | 7.8.13 |
| 6.x | 6.0.0 à 6.13.11 | 6.13.12 |
Le tableau utilise le seuil de correction le plus élevé des trois avis. La faille countDocuments seule a été corrigée dans 9.9.2, 8.24.3, 7.8.12 et 6.13.11 le 10 août 2026. Les corrections de connexion et de propriétés héritées sont arrivées plus tard, dans 9.11.4, 8.24.7, 7.8.13 et 6.13.12. Installer uniquement les versions d'août laisse les deux failles High ouvertes.
Vérifiez la version installée :
npm ls mongoose
grep -A2 '"node_modules/mongoose"' package-lock.json
yarn why mongoose
pnpm list mongoose
Cherchez ensuite où la défense est configurée :
grep -R "sanitizeFilter" --include='*.js' --include='*.ts' .
Une connexion créée avec { sanitizeFilter: true } est exposée à CVE-2026-125119 même si connection.get('sanitizeFilter') renvoie true.
Comment corriger ?
Mettez à jour vers la version corrigée de votre ligne majeure et redéployez chaque service qui utilise Mongoose.
- Sur Mongoose 9.x, installez 9.11.4 ou une version ultérieure :
npm install mongoose@9.11.4 - Sur Mongoose 8.x, installez 8.24.7 ou une version ultérieure :
npm install mongoose@8.24.7 - Sur Mongoose 7.x, installez 7.8.13 ou une version ultérieure :
npm install mongoose@7.8.13 - Sur Mongoose 6.x, installez 6.13.12 ou une version ultérieure :
npm install mongoose@6.13.12 - Reconstruisez et redéployez chaque service, puis exécutez vos tests d'authentification et d'autorisation basés sur les comptes avec la nouvelle version.
Si vous ne pouvez pas mettre à jour tout de suite, l'avis countDocuments publie un middleware de contournement. Il applique sanitizeFilter avant l'exécution du compte :
mongoose.plugin(function() {
schema.pre('countDocuments', function() {
// This will always apply `sanitizeFilter` on `countDocuments`
mongoose.sanitizeFilter(this.getFilter());
});
});
Pour la faille de connexion, ne vous appuyez pas sur createConnection(uri, { sanitizeFilter: true }). Définissez l'option globalement avec mongoose.set('sanitizeFilter', true) ou par requête avec .sanitizeFilter(). Ce contournement ne corrige pas la faille des propriétés héritées stockées ; seule la mise à jour le fait. Validez les noms de champs que votre application accepte si vous ne pouvez pas mettre à jour, et rejetez les chemins comme toString, valueOf, constructor et $__schema.
Quelle est la cause racine ?
Le sanitizeFilter de Mongoose enveloppe les objets opérateurs des valeurs de filtre dans $eq, afin qu'une valeur comme { $ne: null } soit comparée littéralement au lieu d'être interprétée comme un opérateur MongoDB. La défense n'est efficace que si chaque chemin de requête l'applique. Ces trois avis sont trois lacunes dans cette promesse.
La première lacune est countDocuments. En 8.24.2, _countDocuments caste les conditions directement au lieu de passer par _castConditions, la fonction qui applique sanitizeFilter :
Query.prototype._countDocuments = async function _countDocuments() {
this._applyTranslateAliases();
try {
this.cast(this.model);
} catch (err) {
this.error(err);
}
_castConditions vérifie les options configurées et appelle sanitizeFilter(this._conditions), mais countDocuments ne l'atteint jamais. La correction fait appeler _castConditions() à la méthode.
La deuxième lacune est l'option de connexion. NativeConnection#createClient() déplace sanitizeFilter des options de connexion vers l'objet config de la connexion et le supprime des options :
if ('sanitizeFilter' in options) {
this.config.sanitizeFilter = options.sanitizeFilter;
delete options.sanitizeFilter;
}
Query.prototype._castConditions en 8.24.6 lit this.model?.db.options?.sanitizeFilter, puis this.model?.base.options?.sanitizeFilter, puis l'option par requête. Il ne lit jamais this.model.db.config.sanitizeFilter, donc l'option de connexion est silencieusement ignorée même si connection.get('sanitizeFilter') renvoie true.
La troisième lacune est celle des propriétés héritées. Mongoose bloque __proto__, constructor et prototype, mais son code d'hydratation et de définition de chemins ne vérifie pas si une propriété appartient à l'objet. Dans init(), un champ stocké nommé toString résout la propriété héritée Object.prototype.toString, qui est vraie, donc Mongoose récurse dans la fonction et copie les sous-champs stockés dessus :
// assume nested object
if (!doc[i]) {
doc[i] = {};
if (!strict && !(i in docSchema.tree) && !(i in docSchema.methods) && !(i in docSchema.virtuals)) {
self[i] = doc[i];
} else if (opts?.virtuals && (i in docSchema.virtuals)) {
self[i] = doc[i];
}
}
init(self, value, doc[i], opts, path + '.');
La même vérification de propriété propre manque dans $__set, qui gère les chemins pointés, et dans utils.mergeClone, qui fusionne les documents de mise à jour. La correction ajoute des contrôles Object.hasOwn avant l'écriture.
Comment la faille est-elle exploitée ?
- Pour CVE-2026-125120, l'application appelle
Model.countDocuments({ username: req.body.username, password: req.body.password })alors quesanitizeFilterest activé. L'attaquant envoie{"password": {"$ne": null}}. - Comme
countDocumentsa sauté_castConditions, l'objet opérateur atteint MongoDB comme opérateur. Le compte renvoie le compte correspondant même lorsque le mot de passe est faux. - Un contrôle d'authentification ou de quota qui traite un compte non nul comme un succès accorde l'accès ou renvoie des données que l'attaquant ne devrait pas voir.
- Pour CVE-2026-125119, l'application crée une connexion avec
sanitizeFilter: trueet transmet{"password": {"$ne": null}}àfindOne. L'option est dansConnection.config, pas dansConnection.options, donc le filtre n'est pas nettoyé et la requête renvoie le premier compte. - Pour CVE-2026-125117, l'attaquant stocke ou soumet un champ nommé
toStringavec une valeur comme{"call": 1}. L'hydratation copie la valeur sur la fonction partagée, et toutes les requêtes suivantes du processus lèventTypeError: _toString.call is not a function. - La même lacune peut atteindre le schéma partagé d'un sous-document, par exemple en définissant
profile.$__schema.paths.email.validatorssur un tableau vide, ce qui supprime le validateurrequiredpour tous les documents du processus.
Les avis contiennent du code de preuve de concept pour les failles de connexion et de propriétés héritées. L'avis critique contient un test qui montre l'opérateur enveloppé dans $eq après la correction. Aucune exploitation publique dans la nature n'est signalée.
Comment se déroule l'attaque ?
Un objet opérateur contrôlé par l'attaquant atteint un chemin de requête qui a sauté le nettoyage, donc MongoDB l'interprète comme un opérateur et renvoie des données ou modifie un état que l'application ne prévoyait pas.
comme { $ne: null }"] --> B["L'application la transmet à
countDocuments ou une connexion"] B --> C["sanitizeFilter est sauté
ou stocké dans la mauvaise option"] C --> D["MongoDB traite la valeur
comme un opérateur"] D --> E["La requête renvoie un enregistrement
ou un compte auquel l'app se fie"] E --> F["Contournement d'authentification
ou exposition de données"]
Que peut faire un attaquant ?
L'impact dépend du chemin. La faille countDocuments permet à un attaquant de modifier le résultat d'un compte que l'application utilise pour l'autorisation, la pagination, les quotas ou la logique métier. La faille de connexion permet de contourner un contrôle de mot de passe et de lire un enregistrement de compte. La faille des propriétés héritées permet d'écraser des fonctions intégrées partagées et de provoquer un déni de service, ou de modifier des validateurs de schéma partagés.
Les conditions sont :
- L'application transmet des objets contrôlés par l'attaquant dans des filtres, des mises à jour ou la construction de documents Mongoose.
- Pour la faille critique, l'application utilise
countDocumentset s'appuie sursanitizeFilter. - Pour la faille de connexion, l'application définit
sanitizeFiltersurcreateConnectionplutôt que globalement ou par requête. - Pour la faille des propriétés héritées, l'application accepte des noms de champs ou des chemins pointés contrôlés par l'attaquant, ou stocke des documents dont les noms de champs sont contrôlés par l'attaquant.
Les avis décrivent la divulgation de données et le déni de service. Ils ne décrivent pas d'exécution de code à distance, et aucune source ne démontre d'exécution de code par ces chemins.
Pourquoi le correctif fonctionne-t-il ?
Les correctifs ferment chaque lacune au moment où le filtre est préparé. Pour countDocuments, la méthode appelle désormais _castConditions, donc sanitizeFilter s'exécute avant que la requête n'atteigne MongoDB :
Query.prototype._countDocuments = async function _countDocuments() {
this._applyTranslateAliases();
-
- try {
- this.cast(this.model);
- } catch (err) {
- this.error(err);
- }
+ this._castConditions();
if (this.error()) {
throw this.error();
Pour l'option de connexion, _castConditions lit désormais d'abord le config de la connexion :
Query.prototype._castConditions = function() {
- let sanitizeFilterOpt = undefined;
- if (this.model?.db.options?.sanitizeFilter != null) {
- sanitizeFilterOpt = this.model.db.options.sanitizeFilter;
- } else if (this.model?.base.options?.sanitizeFilter != null) {
- sanitizeFilterOpt = this.model.base.options.sanitizeFilter;
- } else {
- sanitizeFilterOpt = this._mongooseOptions.sanitizeFilter;
- }
+ const sanitizeFilterOpt = this.model?.db.config?.sanitizeFilter ??
+ this.model?.db.options?.sanitizeFilter ??
+ this.model?.base.options.sanitizeFilter ??
+ this._mongooseOptions.sanitizeFilter;
Pour les propriétés héritées, le code des documents vérifie Object.hasOwn avant d'écrire :
// assume nested object
+ // Skip inherited properties like `Object.prototype.toString`
+ // to avoid setting properties on shared objects
+ if (!Object.hasOwn(doc, i) && i in doc) {
+ continue;
+ }
if (!doc[i]) {
doc[i] = {};
La même vérification arrive dans le setter de chemins pointés et dans l'assistant de fusion. Un champ toString stocké est désormais ignoré au lieu d'être copié sur la fonction partagée, et un chemin pointé qui nomme une propriété héritée devient sans effet.
Les correctifs sont complets pour les trois chemins de code décrits par les avis. Les applications doivent toujours éviter de transmettre des objets de requête bruts dans des filtres où le schéma attend un scalaire ; sanitizeFilter réduit le risque mais ne valide pas la sémantique de l'application.
Comment vérifier si j'ai été compromis ?
Les avis ne publient aucun indicateur de compromission et aucune exploitation dans la nature n'est connue. Les preuves seraient au niveau de l'application, pas une signature de fichier ou de réseau connue.
Cherchez les requêtes qui transportent des objets opérateurs là où votre code attend un scalaire. Si votre application journalise les filtres de requête, cherchez $ne, $gt, $where ou $expr dans les valeurs issues des requêtes :
grep -E '\$(ne|gt|gte|lt|lte|where|expr)' /var/log/your-app.log
Une correspondance est une piste, pas une preuve : du code légitime peut utiliser ces opérateurs. Corrélez-la avec les événements d'authentification ou d'autorisation basés sur les comptes provenant de la même adresse source.
Cherchez le symptôme de déni de service de la faille des propriétés héritées dans le journal de l'application :
grep -E '_toString\.call is not a function|_toString\.call is not' /var/log/your-app.log
Un processus qui commence à lever cette erreur sur chaque requête après le chargement ou la mise à jour d'un document est cohérent avec la faille. Redémarrer le processus efface le symptôme, mais le document stocké reste, donc trouvez et nettoyez l'enregistrement.
Vérifiez si les collections contiennent des documents dont les noms de champs masquent des fonctions intégrées. Cette requête renvoie une piste, pas un verdict, car un schéma légitime peut utiliser ces noms :
db.getCollectionNames().forEach(function(name) {
const hit = db[name].findOne({ $or: [{ toString: { $exists: true } }, { valueOf: { $exists: true } }] });
if (hit) { print(name); }
});
Si vous trouvez un document écrit par un attaquant, supprimez-le, faites tourner les identifiants que le processus de l'application pouvait lire et examinez les enregistrements renvoyés par les requêtes concernées.
Chronologie
- 2026-08-09 : la correction de
countDocumentsest commitée. - 2026-08-10 : Mongoose 6.13.11, 7.8.12, 8.24.3 et 9.9.2 livrent la correction de
countDocuments. - 2026-10-22 : les corrections de connexion et de propriétés héritées sont commitées.
- 2026-10-23 : Mongoose 8.24.7 et 9.11.4 livrent le second lot de corrections.
- 2026-11-09 : les enregistrements CVE-2026-125120, CVE-2026-125119 et CVE-2026-125117 sont publiés.
- 2026-11-10 : les avis GitHub sont publiés.
FAQ
Cela concerne-t-il toutes les applications Mongoose ?
Non. La faille critique exige que l'application appelle countDocuments avec des valeurs de filtre contrôlées par l'attaquant tout en s'appuyant sur sanitizeFilter. La faille de connexion exige que l'option soit définie sur createConnection. La faille des propriétés héritées est plus large : toute application qui charge des documents ou définit des chemins pointés avec des noms de champs contrôlés par l'attaquant peut l'atteindre. La mise à jour est le choix sûr pour les trois.
Existe-t-il une preuve de concept publique ?
Oui pour les failles de connexion et de propriétés héritées. Les deux avis contiennent du code qui démontre le contournement, et le bloc SSVC de la CISA pour CVE-2026-125117 enregistre Exploitation: poc. L'avis countDocuments contient un test de régression plutôt qu'un exploit autonome.
Est-elle exploitée dans la nature ?
Aucune exploitation dans la nature n'est connue au 11 novembre 2026, et aucun des trois CVE ne figure dans le catalogue Known Exploited Vulnerabilities de la CISA. Le code de preuve de concept public signifie que la fenêtre entre la divulgation et les tentatives est courte, donc mettez à jour selon le calendrier normal sans attendre un signalement.
Quelle version est le seuil sûr ?
Utilisez 6.13.12, 7.8.13, 8.24.7 ou 9.11.4. Les versions d'août ne corrigent que la faille critique countDocuments ; celles d'octobre ajoutent les corrections de connexion et de propriétés héritées. Installer 8.24.3 ou 9.9.2 laisse deux failles High ouvertes.
Pourquoi les avis sont-ils apparus des mois après les corrections ?
Les corrections sont sorties en août et octobre 2026, mais les avis GitHub et les enregistrements CVE ont été publiés les 9 et 10 novembre 2026. Cet écart est courant pour les avis de dépôt examinés plus tard. Ces dates viennent des tags de version et des enregistrements CVE, et l'article utilise les dates de correction, pas celles des avis, pour décrire l'historique.
Dois-je mettre à jour si je n'utilise pas sanitizeFilter ?
Oui, car la faille des propriétés héritées ne dépend pas de sanitizeFilter. Elle dépend de noms de champs contrôlés par l'attaquant qui atteignent l'hydratation des documents, le setter de chemins pointés ou le code de fusion. Si votre schéma accepte des clés arbitraires, la faille s'applique même lorsque la défense de filtre est désactivée.
Trouver les versions vulnérables de Mongoose avec Ostorlab
L'analyse de composition logicielle et le SBOM d'Ostorlab inventorient les dépendances open source de vos dépôts et images de conteneurs, afin de voir quels services résolvent encore mongoose vers une version concernée et de suivre la mise à jour vers 6.13.12, 7.8.13, 8.24.7 ou 9.11.4. Lancez-la sur les dépôts qui construisent vos services Node.js et utilisez les résultats pour prioriser ceux qui transmettent des données de requête dans des filtres.
Références
- Avis Mongoose GHSA-pcrw-h46h-c93h (CVE-2026-125120) : https://github.com/Automattic/mongoose/security/advisories/GHSA-pcrw-h46h-c93h (vérifié le 11 novembre 2026)
- Avis Mongoose GHSA-gg4r-jv3p-qx3c (CVE-2026-125119) : https://github.com/Automattic/mongoose/security/advisories/GHSA-gg4r-jv3p-qx3c (vérifié le 11 novembre 2026)
- Avis Mongoose GHSA-8332-j7wj-wjw4 (CVE-2026-125117) : https://github.com/Automattic/mongoose/security/advisories/GHSA-8332-j7wj-wjw4 (vérifié le 11 novembre 2026)
- Commit de correction de
countDocuments(7e912af3) : https://github.com/Automattic/mongoose/commit/7e912af3fac52cb0eea4cd71e24d65318916831a (vérifié le 11 novembre 2026) - Commit de correction de l'option de connexion (4bb65947) : https://github.com/Automattic/mongoose/commit/4bb659478be2f30655a7bdd78bc251bdf111cd29 (vérifié le 11 novembre 2026)
- Commit de correction des propriétés héritées (18b5513d) : https://github.com/Automattic/mongoose/commit/18b5513dd463731a2caf0d3395ef1d9eb0735f50 (vérifié le 11 novembre 2026)
- Version Mongoose 9.11.4 : https://github.com/Automattic/mongoose/releases/tag/9.11.4 (vérifié le 11 novembre 2026)
- Version Mongoose 8.24.7 : https://github.com/Automattic/mongoose/releases/tag/8.24.7 (vérifié le 11 novembre 2026)
- Nombre de téléchargements npm de mongoose : https://npm-stat.com/charts.html?package=mongoose&from=2026-11-03&to=2026-11-09 (vérifié le 11 novembre 2026)
- Enregistrement CVE-2026-125120 : https://www.cve.org/CVERecord?id=CVE-2026-125120 (vérifié le 11 novembre 2026)
- Enregistrement CVE-2026-125117 : https://www.cve.org/CVERecord?id=CVE-2026-125117 (vérifié le 11 novembre 2026)