VYPR
High severityNVD Advisory· Published Dec 2, 2024· Updated Jan 6, 2025

CVE-2024-53900

CVE-2024-53900

Description

Mongoose before 8.8.3 fails to sanitize $where in match conditions, enabling search injection attacks.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Mongoose before 8.8.3 fails to sanitize $where in match conditions, enabling search injection attacks.

Vulnerability

Description Mongoose versions before 8.8.3 contain a search injection vulnerability related to the improper use of the $where operator in match conditions [1]. This flaw allows an attacker to inject arbitrary JavaScript expressions into database queries, bypassing the intended query logic. The root cause is insufficient input validation when handling the $where clause within MongoDB aggregation pipelines or find operations.

Exploitation

To exploit this vulnerability, an attacker must be able to supply untrusted input that is used in a match stage or similar query context. No special privileges are required beyond the ability to provide crafted data to an application using Mongoose. The attacker can inject a $where clause containing arbitrary JavaScript, which will be executed by the MongoDB server as part of the query [2].

Impact

Successful exploitation can lead to unauthorized data access, retrieval of sensitive information, or bypassing security controls. Since $where allows server-side JavaScript execution, it may also enable more severe attacks depending on the database configuration, though the primary impact is information disclosure via search injection [3].

Mitigation

The issue is fixed in Mongoose version 8.8.3 and later. Users should upgrade to the patched version immediately. For applications that cannot be updated immediately, avoid using user-supplied data directly in query conditions containing $where and implement strict input validation [4].

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
mongoosenpm
>= 8.0.0-rc0, < 8.8.38.8.3
mongoosenpm
>= 7.0.0-rc0, < 7.8.37.8.3
mongoosenpm
>= 6.0.0-rc0, < 6.13.56.13.5
mongoosenpm
>= 3.6.0-rc0, < 5.13.235.13.23

Affected products

3

Patches

3
bbb6fa7ecb44

Merge pull request #15105 from Automattic/vkarpov15/gh-15078

https://github.com/Automattic/mongooseValeri KarpovDec 17, 2024via ghsa
1 file changed · +9 0
  • lib/helpers/populate/getModelsMapForPopulate.js+9 0 modified
    @@ -207,6 +207,15 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
         if (hasMatchFunction) {
           match = match.call(doc, doc);
         }
    +    if (Array.isArray(match)) {
    +      for (const item of match) {
    +        if (item != null && item.$where) {
    +          throw new MongooseError('Cannot use $where filter with populate() match');
    +        }
    +      }
    +    } else if (match != null && match.$where != null) {
    +      throw new MongooseError('Cannot use $where filter with populate() match');
    +    }
     
         if (Array.isArray(localField) && Array.isArray(foreignField) && localField.length === foreignField.length) {
           match = Object.assign({}, match);
    
33679bcf8ca4

fix: disallow using $where in match

https://github.com/Automattic/mongooseValeri KarpovNov 21, 2024via ghsa
3 files changed · +66 5
  • lib/helpers/populate/assignVals.js+1 5 modified
    @@ -243,7 +243,7 @@ function numDocs(v) {
     
     function valueFilter(val, assignmentOpts, populateOptions, allIds) {
       const userSpecifiedTransform = typeof populateOptions.transform === 'function';
    -  const transform = userSpecifiedTransform ? populateOptions.transform : noop;
    +  const transform = userSpecifiedTransform ? populateOptions.transform : v => v;
       if (Array.isArray(val)) {
         // find logic
         const ret = [];
    @@ -335,7 +335,3 @@ function isPopulatedObject(obj) {
         obj.$__ != null ||
         leanPopulateMap.has(obj);
     }
    -
    -function noop(v) {
    -  return v;
    -}
    
  • lib/helpers/populate/getModelsMapForPopulate.js+19 0 modified
    @@ -182,6 +182,15 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
         if (hasMatchFunction) {
           match = match.call(doc, doc);
         }
    +    if (Array.isArray(match)) {
    +      for (const item of match) {
    +        if (item != null && item.$where) {
    +          throw new MongooseError('Cannot use $where filter with populate() match');
    +        }
    +      }
    +    } else if (match != null && match.$where != null) {
    +      throw new MongooseError('Cannot use $where filter with populate() match');
    +    }
         data.match = match;
         data.hasMatchFunction = hasMatchFunction;
         data.isRefPath = isRefPath;
    @@ -460,6 +469,16 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
         data.match = match;
         data.hasMatchFunction = hasMatchFunction;
     
    +    if (Array.isArray(match)) {
    +      for (const item of match) {
    +        if (item != null && item.$where) {
    +          throw new MongooseError('Cannot use $where filter with populate() match');
    +        }
    +      }
    +    } else if (match != null && match.$where != null) {
    +      throw new MongooseError('Cannot use $where filter with populate() match');
    +    }
    +
         // Get local fields
         const ret = _getLocalFieldValues(doc, localField, model, options, virtual);
     
    
  • test/model.populate.test.js+46 0 modified
    @@ -4166,6 +4166,52 @@ describe('model: populate:', function() {
             assert.deepEqual(band.members.map(b => b.name).sort(), ['AA', 'AB']);
           });
     
    +      it('match prevents using $where', async function() {
    +        const ParentSchema = new Schema({
    +          name: String,
    +          child: {
    +            type: mongoose.Schema.Types.ObjectId,
    +            ref: 'Child'
    +          },
    +          children: [{
    +            type: mongoose.Schema.Types.ObjectId,
    +            ref: 'Child'
    +          }]
    +        });
    +
    +        const ChildSchema = new Schema({
    +          name: String
    +        });
    +        ChildSchema.virtual('parent', {
    +          ref: 'Parent',
    +          localField: '_id',
    +          foreignField: 'parent'
    +        });
    +
    +        const Parent = db.model('Parent', ParentSchema);
    +        const Child = db.model('Child', ChildSchema);
    +
    +        const child = await Child.create({ name: 'Luke' });
    +        const parent = await Parent.create({ name: 'Anakin', child: child._id });
    +
    +        await assert.rejects(
    +          () => Parent.findOne().populate({ path: 'child', match: { $where: 'console.log("oops!");' } }),
    +          /Cannot use \$where filter with populate\(\) match/
    +        );
    +        await assert.rejects(
    +          () => Parent.find().populate({ path: 'child', match: { $where: 'console.log("oops!");' } }),
    +          /Cannot use \$where filter with populate\(\) match/
    +        );
    +        await assert.rejects(
    +          () => parent.populate({ path: 'child', match: { $where: 'console.log("oops!");' } }),
    +          /Cannot use \$where filter with populate\(\) match/
    +        );
    +        await assert.rejects(
    +          () => Child.find().populate({ path: 'parent', match: { $where: 'console.log("oops!");' } }),
    +          /Cannot use \$where filter with populate\(\) match/
    +        );
    +      });
    +
           it('multiple source docs', function(done) {
             const PersonSchema = new Schema({
               name: String,
    
c9e86bff7eef

fix: disallow using $where in match

https://github.com/Automattic/mongooseValeri KarpovNov 21, 2024via ghsa
3 files changed · +66 5
  • lib/helpers/populate/assignVals.js+1 5 modified
    @@ -249,7 +249,7 @@ function numDocs(v) {
     
     function valueFilter(val, assignmentOpts, populateOptions, allIds) {
       const userSpecifiedTransform = typeof populateOptions.transform === 'function';
    -  const transform = userSpecifiedTransform ? populateOptions.transform : noop;
    +  const transform = userSpecifiedTransform ? populateOptions.transform : v => v;
       if (Array.isArray(val)) {
         // find logic
         const ret = [];
    @@ -341,7 +341,3 @@ function isPopulatedObject(obj) {
         obj.$__ != null ||
         leanPopulateMap.has(obj);
     }
    -
    -function noop(v) {
    -  return v;
    -}
    
  • lib/helpers/populate/getModelsMapForPopulate.js+19 0 modified
    @@ -184,6 +184,15 @@ module.exports = function getModelsMapForPopulate(model, docs, options) {
         if (hasMatchFunction) {
           match = match.call(doc, doc);
         }
    +    if (Array.isArray(match)) {
    +      for (const item of match) {
    +        if (item != null && item.$where) {
    +          throw new MongooseError('Cannot use $where filter with populate() match');
    +        }
    +      }
    +    } else if (match != null && match.$where != null) {
    +      throw new MongooseError('Cannot use $where filter with populate() match');
    +    }
         data.match = match;
         data.hasMatchFunction = hasMatchFunction;
         data.isRefPath = isRefPath;
    @@ -447,6 +456,16 @@ function _virtualPopulate(model, docs, options, _virtualRes) {
         data.match = match;
         data.hasMatchFunction = hasMatchFunction;
     
    +    if (Array.isArray(match)) {
    +      for (const item of match) {
    +        if (item != null && item.$where) {
    +          throw new MongooseError('Cannot use $where filter with populate() match');
    +        }
    +      }
    +    } else if (match != null && match.$where != null) {
    +      throw new MongooseError('Cannot use $where filter with populate() match');
    +    }
    +
         // Get local fields
         const ret = _getLocalFieldValues(doc, localField, model, options, virtual);
     
    
  • test/model.populate.test.js+46 0 modified
    @@ -3641,6 +3641,52 @@ describe('model: populate:', function() {
             assert.deepEqual(band.members.map(b => b.name).sort(), ['AA', 'AB']);
           });
     
    +      it('match prevents using $where', async function() {
    +        const ParentSchema = new Schema({
    +          name: String,
    +          child: {
    +            type: mongoose.Schema.Types.ObjectId,
    +            ref: 'Child'
    +          },
    +          children: [{
    +            type: mongoose.Schema.Types.ObjectId,
    +            ref: 'Child'
    +          }]
    +        });
    +
    +        const ChildSchema = new Schema({
    +          name: String
    +        });
    +        ChildSchema.virtual('parent', {
    +          ref: 'Parent',
    +          localField: '_id',
    +          foreignField: 'parent'
    +        });
    +
    +        const Parent = db.model('Parent', ParentSchema);
    +        const Child = db.model('Child', ChildSchema);
    +
    +        const child = await Child.create({ name: 'Luke' });
    +        const parent = await Parent.create({ name: 'Anakin', child: child._id });
    +
    +        await assert.rejects(
    +          () => Parent.findOne().populate({ path: 'child', match: { $where: 'console.log("oops!");' } }),
    +          /Cannot use \$where filter with populate\(\) match/
    +        );
    +        await assert.rejects(
    +          () => Parent.find().populate({ path: 'child', match: { $where: 'console.log("oops!");' } }),
    +          /Cannot use \$where filter with populate\(\) match/
    +        );
    +        await assert.rejects(
    +          () => parent.populate({ path: 'child', match: { $where: 'console.log("oops!");' } }),
    +          /Cannot use \$where filter with populate\(\) match/
    +        );
    +        await assert.rejects(
    +          () => Child.find().populate({ path: 'parent', match: { $where: 'console.log("oops!");' } }),
    +          /Cannot use \$where filter with populate\(\) match/
    +        );
    +      });
    +
           it('multiple source docs', async function() {
             const PersonSchema = new Schema({
               name: String,
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

13

News mentions

0

No linked articles in our index yet.