Mongoose `populate()` Match `$where` Bypass Command Injection (CVE-2025-23061)
by dajneem23 · 2026-07-06
- Severity
- Critical
- CVE
- CVE-2025-23061
- Category
- web
- Affected product
- Mongoose (Node.js MongoDB ODM)
- Affected versions
- < 6.13.6, >= 7.0.0 < 7.8.4, >= 8.0.0 < 8.9.5
- Disclosed
- 2026-07-06
- Patch status
- unpatched
Tags
References
- https://github.com/dajneem23/CVE-2025-23061
- https://github.com/Automattic/mongoose/commit/64a9f9706f2428c49e0cfb8e223065acc645f7bc
- https://github.com/Automattic/mongoose/releases/tag/8.9.5
- https://nvd.nist.gov/vuln/detail/CVE-2025-23061
- https://github.com/projectdiscovery/nuclei-templates/blob/main/http/cves/2025/CVE-2025-23061.yaml
- https://github.com/NamhyeonKo/mongoose-cve-lab
- https://www.opswat.com/blog/technical-discovery-mongoose-cve-2025-23061-cve-2024-53900
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-06 |
| Last Updated | 2026-07-06 |
| Author / Researcher | dajneem23 |
| CVE / Advisory | CVE-2025-23061 |
| Category | web |
| Severity | Critical |
| CVSS Score | 9.0 (per NVD) |
| Status | Weaponized |
| Tags | mongoose, nodejs, nosql-injection, mongodb, populate, where-operator, command-injection, rce, cwe-943, cwe-94, express |
| Related | CVE-2024-53900 |
Affected Target
| Field | Value |
|---|---|
| Software / System | Mongoose (Node.js MongoDB ODM) |
| Versions Affected | < 6.13.6, >= 7.0.0 < 7.8.4, >= 8.0.0 < 8.9.5 |
| Language / Platform | JavaScript / Node.js + Express + MongoDB |
| Authentication Required | No |
| Network Access Required | Yes |
Summary
CVE-2025-23061 is an incomplete-fix bypass of CVE-2024-53900, a NoSQL/command injection vulnerability in the Mongoose ODM for Node.js. The original fix blocked $where operators submitted directly inside a populate() match filter, but failed to sanitize $where when it is nested inside logical operators such as $and and $or, allowing attacker-controlled JavaScript to be executed inside MongoDB’s (or, via mongo-eval-style constructor abuse, the Node.js process’s) execution context. The included PoC ships an Express + Mongoose server with endpoints that pass unsanitized, user-supplied JSON straight into populate({ match }), plus an exploit.js client demonstrating information disclosure, authentication bypass, denial-of-service (infinite loops/sleeps), and remote code execution via this.constructor.constructor(...) / global.process.mainModule.require(...) sandbox-escape tricks. Impact ranges from silent data exfiltration to full RCE on the application/database host.
Vulnerability Details
Root Cause
Mongoose’s $where sanitization for populate().match only inspected the top level of the match object. Placing $where one level deeper, inside $and/$or arrays, bypassed the check entirely, so the raw JavaScript string was still forwarded to MongoDB’s $where evaluator (which runs arbitrary JS server-side):
| |
An attacker-supplied authorMatch of {"$and":[{"$where":"this.isAdmin"}]} slips past the (incomplete) top-level $where filter because the operator is nested inside $and.
Attack Vector
- Identify a Node.js/Express endpoint that accepts JSON (query string, POST body, or a
populate()/view[...]style parameter) and forwards it into a Mongoosepopulate({ match })call without validation, on a Mongoose version in the vulnerable range. - Craft a match object where
$whereis nested inside a logical operator, e.g.{"$and":[{"$where":"this.isAdmin"}]}, to bypass the CVE-2024-53900 patch’s top-level check. - Submit the payload (
GET /posts?authorMatch=<json>orPOST /posts/searchwith{ authorMatch: {...} }). - MongoDB evaluates the injected JavaScript expression as part of the query. Simple expressions leak data (e.g.
this.isAdmin,this.email.includes('admin')); boolean/sleep expressions enable blind extraction or DoS (sleep(1000)||true,while(true){}); and JS-sandbox-escape expressions such asthis.constructor.constructor("return process")()orglobal.process.mainModule.require('child_process').execSync(...)can reach the Node.js process itself for command execution. - Response content/timing differences confirm exploitation and can be used to enumerate arbitrary boolean conditions or exfiltrate data field-by-field.
Impact
- Information disclosure: read arbitrary fields/documents regardless of intended access control (e.g. admin flags, emails, passwords).
- Authentication bypass: force
populate.matchconditions to always evaluate true, defeating authorization checks built on match filters. - Denial of service: inject
sleep()/infinite-loop$whereexpressions to exhaust MongoDB worker threads. - Remote code execution: escape the
$whereJS sandbox via constructor-chain tricks to reachprocess/require, enabling arbitrary command execution on the server (demonstrated inexploit.jsreading/etc/passwd, writing files, making outbound HTTP callbacks, or exiting the process).
Environment / Lab Setup
- Node.js >= 14
- mongoose 8.9.4 (or any version in the vulnerable ranges: <6.13.6, >=7.0.0 <7.8.4, >=8.0.0 <8.9.5)
- express ^4.18.2
- MongoDB 7.0 reachable at mongodb://localhost:27017/mongoose_cve_poc
(docker-compose.yml in this folder spins up a local mongo:7.0 container)
- npm install && npm start # starts the vulnerable server on :3000
Proof of Concept
PoC Script
See
server.js(vulnerable app) andexploit.js(exploit client) in this folder.
| |
Detection & Indicators of Compromise
- HTTP requests with query/body parameters containing raw MongoDB operators:
"$where", combined with "$and"/"$or" nesting, in fields named match/authorMatch/query/view.
- Application logs (debug namespaces cve-2025-23061-server / cve-2025-23061-exploit)
showing "Received query:" entries containing $where clauses.
- Anomalous MongoDB server-side JS evaluation errors or unexpectedly long query
times (console.time('QueryTime') style latency spikes from sleep()/while(true) payloads).
- Outbound connections from the MongoDB/app host to unexpected external hosts
(indicative of require('https')/exec-based exfiltration callbacks).
- New/unexpected files written to disk (e.g. /tmp/rce-23061-triggered.txt) or
unexpected child processes spawned by the Node.js server process.
Signs of compromise:
- Unexplained
isAdmin/privileged-field data appearing in responses to unauthenticated or low-privilege requests. - MongoDB or application process CPU/latency spikes correlated with
populate/matchrequests. - Node.js server process spawning shell commands (
child_process.execSync) it never should. - Unexpected outbound HTTP requests originating from the database/application tier.
- New files or process exits with no corresponding legitimate admin action.
Remediation
| Action | Detail |
|---|---|
| Primary fix | Upgrade mongoose to >= 8.9.5 (also ensure >= 7.8.4 or >= 6.13.6 for the 6.x/7.x lines), which fully sanitizes $where nested inside logical operators. See the official fix commit. |
| Interim mitigation | Never pass raw, unvalidated user input into populate({ match }), find(), or any Mongoose query builder. Explicitly strip/reject $where (and any keys nested under $and/$or/$nor) from client-supplied filter objects, disable the MongoDB server-side $where/mapReduce JS execution where not required (security.javascriptEnabled: false for MongoDB self-managed deployments), and apply strict allow-lists for query fields instead of accepting arbitrary JSON. |
References
- Source repository
- GitHub Commit (Fix)
- Mongoose Release 8.9.5
- NVD CVE-2025-23061
- Nuclei Template
- Mongoose CVE Lab
- OPSWAT technical writeup: CVE-2025-23061 / CVE-2024-53900
Notes
Mirrored from https://github.com/dajneem23/CVE-2025-23061 on 2026-07-06. Repo was transferred between GitHub accounts per prior verification; scanner.js in the source repo is an empty placeholder file with no functional content.
| |