From c9e86bff7eef477da75a29af62a06d41a835a156 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Thu, 21 Nov 2024 17:10:56 -0500 Subject: [PATCH] fix: disallow using $where in match Fix CVE-2024-53900 --- lib/helpers/populate/assignVals.js | 6 +-- .../populate/getModelsMapForPopulate.js | 19 ++++++++ test/model.populate.test.js | 46 +++++++++++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/lib/helpers/populate/assignVals.js b/lib/helpers/populate/assignVals.js index 9aff29fd538..62b3863b583 100644 --- a/lib/helpers/populate/assignVals.js +++ b/lib/helpers/populate/assignVals.js @@ -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; -} diff --git a/lib/helpers/populate/getModelsMapForPopulate.js b/lib/helpers/populate/getModelsMapForPopulate.js index 16d920366a8..bd748ed0722 100644 --- a/lib/helpers/populate/getModelsMapForPopulate.js +++ b/lib/helpers/populate/getModelsMapForPopulate.js @@ -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); diff --git a/test/model.populate.test.js b/test/model.populate.test.js index ef827fbc3bf..f62e5886c9e 100644 --- a/test/model.populate.test.js +++ b/test/model.populate.test.js @@ -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,