Mongoose is an Object Data Modeling (ODM) library for MongoDB that simplifies database interactions in Node.js applications. By providing a schema-based solution, Mongoose enables JavaScript objects to be mapped to MongoDB documents, acting as an abstraction layer that helps structure data for easier management and validation. With features like middleware for custom logic execution and an intuitive query-building system, Mongoose enhances the efficiency of working with MongoDB. Mongoose, described as "elegant MongoDB object modeling for Node.js", has garnered 27K stars on GitHub, reflecting its widespread use and appreciation among developers.
OPSWAT Fellowship Program and Critical Vulnerability Discovery
The OPSWAT Critical Infrastructure Cybersecurity Graduate Fellowship Program, based in Vietnam, provides graduate students with hands-on experience in securing critical infrastructure. As part of this program, fellows have the opportunity to analyze and address cybersecurity vulnerabilities, collaborating with OPSWAT experts to tackle real-world challenges in areas like malware detection, file security, and threat prevention.
During the OPSWAT Fellowship program, participants systematically investigate and reproduce known CVEs across various products, libraries, and operating systems. As part of this initiative, Dat Phung - one of our distinguished fellows - chose to examine Mongoose due to its widespread adoption in production environments. In November 2024, he discovered a critical vulnerability in Mongoose while performing an in-depth analysis of the library. The vulnerability allowed an attacker to exploit the $where value, potentially leading to Remote Code Execution (RCE) on the Node.js application server. Upon promptly reporting the issue to Mongoose, a patch was released as part of version 8.8.3, and the CVE-2024-53900 was disclosed in the National Vulnerability Database (NVD).
CVE-2024-53900 & CVE-2025-23061 Timeline
- November 7, 2024: Dat Phung identified a critical vulnerability in Mongoose and submitted a security report to Snyk.
- November 26, 2024: Mongoose released version 8.8.3 to address and fix this vulnerability.
- December 2, 2024: The National Vulnerability Database (NVD) disclosed CVE-2024-53900 for this vulnerability.
- December 17, 2024: Upon analyzing Mongoose’s 8.8.3 patch, Dat Phung found a bypass that still enabled RCE (Remote Code Execution). A detailed security report was submitted to Tidelift.
- January 13, 2025: Mongoose released version 8.9.5, introducing an enhanced patch that effectively addressed the bypass.
- January 15, 2025: The National Vulnerability Database (NVD) officially disclosed CVE-2025-23061, emphasizing the severity of the newly identified vulnerability.
Mongoose's Populate() Method
Mongoose also provides a useful feature called populate() that enhances the ability to work with relationships between documents. While MongoDB versions ≥ 3.2 have the $lookup aggregation operator for joins, Mongoose's populate() offers a more powerful alternative for automatically replacing a reference with the corresponding data from related documents. This is particularly useful for managing relationships between different MongoDB collections, such as when one document references another by its _id. [2]
When defining a schema in Mongoose, a field can be set to reference another model using the ref option. The populate() method is then used to replace the referenced field (an ObjectId) with the full document from the related model. For example, in a book store application, the author field in the bookSchema references the Author document, and the review field references the Reviews document. The populate() method enables developers to replace the author field (which is an ObjectId) with the full Author document when querying the book model.
The populate() allows developers to replace author field (which is an ObjectId) with the full Author document when query book model:
Furthermore, Mongoose's populate() method supports custom queries to define which related documents are retrieved and how they are fetched. Properties like match and options allow developers to filter, sort, limit, and skip related documents, offering flexible data retrieval capabilities.
CVE-2024-53900 Analysis
As part of the OPSWAT Cybersecurity Graduate Fellowship program, while analyzing Mongoose to reproduce known CVEs, Dat Phung conducted a comprehensive review of the internal workings of the populate() method, which plays a key role in handling relationships between MongoDB documents. The populate() method supports both string and object arguments, and developers can use the match option to apply specific filters on the data being retrieved:
In the example above, the match option is a filter object that can include MongoDB query operators, as detailed in the Query and Projection Operators - MongoDB Manual v8.0. One notable operator is $where, which enables JavaScript execution directly on the MongoDB server. However, this execution on MongoDB server is restricted, supporting only basic operations and functions.
Dat Phung conducted an in-depth analysis of the Mongoose source code to understand the workflow of the populate() method. He determined that after the application calls the populate() method on the model, the populate() function is triggered. Within this function, Mongoose calls the _execPopulateQuery() function, which executes the query with the $where operator on the MongoDB server. Subsequently, all the documents from the foreign collection are retrieved for population in the next steps.
After retrieving the data from MongoDB, Mongoose executes the callback function _done(), which calls _assign() to prepare the data before "joining" the two models by calling assignVals() function.
The vulnerability may arise when the retrieved data is processed by Mongoose's assignVals() function. This function checks if the match option is an array and, if so, passes each operator to the sift() function. The sift() function, which is imported from an external library of the same name, processes these queries locally on the application server. This local processing presents a security risk, especially when handling user-controlled input.
To further investigate this, Dat Phung modified the values in the match option to ensure that the conditions were satisfied, thereby invoking the sift() function for additional analysis of the data flow.
With the condition in place, the $where operator was subsequently passed to the sift() function.
The sift library is a lightweight JavaScript utility designed to filter and query data collections such as arrays or JSON objects using MongoDB-like syntax. According to the official documentation, “Sift is a tiny library for using MongoDB queries in JavaScript.” The sift() function evaluates MongoDB-like filter operations on the application server instead of the database server, which can expose the system to significant security risks when processing untrusted input.
Continuing his analysis, our Fellow identified an issue within the createDefaultQueryTester() function of the sift library. This function converts each operation in the match array into executable JavaScript functions, which are then used to filter and process MongoDB document data locally. To achieve this, createDefaultQueryTester() invokes the createNamedOperation() function, passing operations such as $where from the match array as arguments.
For each operation in the match array, createNamedOperation checks if the operation is supported and then passes it to the corresponding function.
If the operation is $where, a JavaScript function is generated using the raw "params" value, which is derived from the $where operator in the match array and can be controlled by user.
CVE-2024-53900: Exploitation Details
While MongoDB limits the execution of JavaScript functions via the $where operation, as previously analyzed, the sift() function allows these functions to be executed without such restrictions. This lack of input validation and restriction introduces a significant security vulnerability, as the "params" value- directly controlled by user input - can be exploited, potentially leading to code injection attacks. To examine this issue more thoroughly, Dat Phung constructed the following query:
Initially, the query failed to execute another process, resulting in the following error:
This error indicates that Mongoose attempts to execute the $where operation on the MongoDB server before passing control to the sift() function. However, due to the restrictions on JavaScript functions in MongoDB's $where clause, an error occurs, preventing the query from executing. As a result, Mongoose halts the process before it can reach the sift() function.
To bypass the limitation, our Fellow leveraged the "global" variable present on the application server, which does not exist on the MongoDB server. This approach allowed him to bypass the restriction on MongoDB server and enable the query to reach the sift() function:
With this value, when Mongoose executes the $where operation on MongoDB, the absence of the "global" variable causes the ternary operator (typeof global != "undefined" ?global.process.mainModule.constructor._load("child_process").exec("calc") : 1) to return 1, preventing MongoDB from throwing an error. Consequently, the query is executed on the MongoDB server without issues.
However, when the same value reaches the sift() function, which runs on the application server where the "global" variable is available, it triggers the creation of the following function:
Remote Code Execution (RCE) Proof of Concept
In the application example provided at the beginning of the blog, if an attacker were to send the following request, they could successfully execute a Remote Code Execution (RCE) attack:
The video demonstrates the Proof of Concept for CVE-2024-53900 affecting Mongoose versions prior to 8.8.3, which lacks proper input validation to prevent misuse of the $where operator alongside the sift library.
Incomplete Fix and CVE-2025-23061
Based on Dat Phung’s security report, Mongoose introduced a patch aimed at resolving the previously identified vulnerability (CVE-2024-53900) before its public disclosure. The relevant patch (Automattic/mongoose@33679bc) added a check to disallow $where usage within the match property passed to populate().
This snippet checks whether the match property passed into populate() is an array. If it is, the code iterates through each object in the array to see if it contains the $where operator. If $where is detected, an error is raised, preventing the malicious payload from propagating to the risky sift() function.
As a result, the payload exploiting CVE-2024-53900 fails this check because an object in the match array contains $where, effectively blocking it from reaching sift().
While this update correctly blocks direct usage of $where within a single nesting level, it fails to detect $where when embedded inside an $or operator - a structure both MongoDB and the sift library fully support.
As a result, an attacker can nest $where under $or to evade the patch’s single-level check. Because Mongoose inspects only the top-level properties of each object in the match array, the bypass payload remains undetected and eventually reaches the sift library, enabling the malicious RCE.
Proof of Concept for CVE-2025-23061
To illustrate the incomplete nature of the fix, Dat Phung rebuilt the example application using a version of Mongoose 8.9.4 (later than 8.8.3). By nesting $where inside an $or clause, an attacker can successfully bypass the check and achieve RCE.
The proof-of-concept exploit demonstrates how CVE-2025-23061 can be triggered in Mongoose versions prior to 8.9.5, allowing an attacker to execute arbitrary code on the server:
Mitigation and Guidance
To mitigate the vulnerabilities we discussed above, please ensure that your system is updated to the latest version of Mongoose.
MetaDefender Core Using SBOM Engine Can Detect This Vulnerability
OPSWAT MetaDefender Core, equipped with advanced SBOM (Software Bill of Materials) capabilities, enables organizations to take a proactive approach in addressing security risks. By scanning software applications and their dependencies, MetaDefender Core identifies known vulnerabilities, such as CVE-2024-53900 and CVE-2025-23061, within the listed components. This empowers development and security teams to prioritize patching efforts, mitigating potential security risks before they can be exploited by malicious actors.
Below is a screenshot of CVE-2024-53900 and CVE-2025-23061, which were detected by MetaDefender Core with SBOM:
Additionally, the CVEs can also be detected by MetaDefender Software Supply Chain, which leverages MetaDefender Core with SBOM to identify these vulnerabilities.