A new vulnerability, CVE-2025-23061, has been found in Mongoose, a widely-used MongoDB object modeling tool for Node.js. Versions before 8.9.5 are affected. This bug lets attackers abuse a nested $where filter within a populate().match call, leading to search injection—in other words, attackers can inject or manipulate database queries in ways developers didn’t expect. This is closely related to an earlier flaw, CVE-2024-53900, but CVE-2025-23061 essentially reveals that the previous fix was incomplete.
In this post, we’ll break down what’s happening, show some code, and explain how an attacker could exploit this. If you use Mongoose for backend JavaScript/Node apps—keep reading.
Quick Background: What is populate() and $where?
Mongoose’s populate() function is a common feature for joining related documents (like SQL JOINs). The match option allows you to add filters to the population process.
The $where operator in MongoDB is dangerous: it allows queries using arbitrary JavaScript in the database. If an attacker can control anything passed to $where, they may be able to run their own code or mess up the queries.
The Core of CVE-2025-23061
The problem appears when you nest a $where filter inside the match option in populate(). Due to missing or inadequate checks in versions before 8.9.5, user input could slip through into the $where clause, triggering search injection.
MongoDB’s $where lets you pass a function or a string, like so
db.users.find({ $where: "this.name == 'Bob'" })
If untrusted input ends up here, bad things can happen!
Imagine a simple Mongoose schema
// Post and User schemas
const UserSchema = new mongoose.Schema({ name: String });
const PostSchema = new mongoose.Schema({
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
});
const User = mongoose.model('User', UserSchema);
const Post = mongoose.model('Post', PostSchema);
Suppose your API lets users filter posts by author name using a query like
// Example express route
router.get('/posts', async (req, res) => {
const name = req.query.name; // User-controlled input!
const posts = await Post.find()
.populate({
path: 'author',
match: { name: name }
});
res.json(posts);
});
This looks safe if name is "Alice" or "Bob", right? But if an attacker passes something sneaky, like:
name[$where]=this.password%20!=%20undefined
Mongoose’s population logic before 8.9.5 didn’t properly sanitize this. It could end up adding a $where clause with attacker-controlled code. In some cases, it could even let the attacker read data they shouldn't, or at least modify query logic.
`http
GET /posts?name=Alice
`http
GET /posts?name[$where]=this.isAdmin==true
`json
{
}
}
`
This can force populate() to fetch any user documents where isAdmin is true (or whatever code the attacker injects).
Note: If the attacker controls the string, they can also test for existence of fields, check for certain user info, or even execute more complex logic. MongoDB disables $where by default for Atlas, but for self-hosted or older setups, this is dangerous.
The Patch
Mongoose 8.9.5 (see release notes) tightens the checks thoroughly, ensuring that only trusted values can get into $where. The previous fix in CVE-2024-53900 was incomplete and still let some edge cases slip through.
Protecting Your App
Upgrade to at least Mongoose 8.9.5. This is non-negotiable if your app relies on user-generated queries.
In addition, never let user input directly control query operators or functions like $where. If you allow flexible filtering, validate and sanitize all input carefully.
Manual patch example:
Here’s how to block $where from sneaking in—before the fix, you could do
function sanitize(obj) {
if (typeof obj !== 'object' || obj == null) return obj
for (const key in obj) {
if (key === '$where') delete obj[key]
else sanitize(obj[key])
}
}
router.get('/posts', async (req, res) => {
const match = { name: req.query.name }
sanitize(match)
const posts = await Post.find().populate({ path: 'author', match })
res.json(posts)
});
References
- Official CVE-2025-23061 NVD entry (pending update)
Mongoose GitHub:
- 8.9.5 release notes
- Previous CVE-2024-53900 advisory
- Mongoose populate() documentation
- MongoDB $where operator docs
Conclusion
CVE-2025-23061 shows how tricky query injection can be—even in battle-tested libraries like Mongoose. If you use Mongoose and expose values to populate().match, update as soon as possible. Don't let user input anywhere near MongoDB operators like $where. Always validate incoming data, even after patching, and stay alert for future advisories!
Timeline
Published on: 01/15/2025 05:15:10 UTC