AI-Powered Cyberattacks: How to Detect, Prevent & Defend Against Intelligent Threats

Read Now
We utilize artificial intelligence for site translations, and while we strive for accuracy, they may not always be 100% precise. Your understanding is appreciated.

Technical Discovery of Mongoose CVE-2025-23061 and CVE-2024-53900

by OPSWAT
Share this Post

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] 

JavaScript code defining a Mongoose schema for an author with name, age, and bio fields
Authors Schema
JavaScript code defining a Mongoose schema for books, including title, description, price, and author references
Books Schema
JavaScript code snippet for a Node.js route that retrieves book details and populates author or review fields
Nodejs application

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:

JSON representation of a MongoDB document storing book details without expanded author information
Not using populate()
JSON representation of a MongoDB document with expanded author details using the populate function
Using populate()

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.

JavaScript code demonstrating a custom query using Mongoose’s populate function to filter authors by age
Custom query in populate() in Mongoose

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:

JavaScript code showcasing the use of the $where operator to filter authors by age in a query

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.

Table listing JavaScript functions and properties available for MongoDB map-reduce operations

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.

Screenshot of a VS Code debugging session analyzing the execution of a Mongoose populate query

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.

A debugging session in VS Code showing highlighted JavaScript code related to the $where query in Mongoose

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.

A VS Code debug session showing variable assignments inside a JavaScript function

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.

A JavaScript code snippet demonstrating a Mongoose query with a $where filter condition

With the condition in place, the $where operator was subsequently passed to the sift() function.

A VS Code debug session showing filtered values in a JavaScript 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.

A simple JavaScript snippet using Sift.js to filter an array of objects based on age criteria
A Sift code example

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. 

A VS Code debugging session focusing on query operations and function execution

For each operation in the match array, createNamedOperation checks if the operation is supported and then passes it to the corresponding function.

A JavaScript debugging session showing query operators like $where, $eq, and $exists

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.

A highlighted JavaScript snippet demonstrating function execution when CSP (Content Security Policy) is enabled

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:

A JavaScript snippet showing a Mongoose query using the $where condition with a potentially unsafe function execution

Initially, the query failed to execute another process, resulting in the following error:

A MongoDB error log displaying a server execution failure due to an undefined global reference

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:

A computer monitor showing the text, "the code is not working," highlighting a problem in the coding process

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:

A monochrome screen featuring the phrase "hello world" in bold text

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:

Black and white screen displaying the text "the new york times" prominently in a bold font
Diagram illustrating the technical details of Mongoose CVE vulnerabilities, showing data flow and security breach points

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.

MongoDB supports $or operator
Sift library supports $or operator

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.

Payload used to bypass the fix in mongoose 8.8.3

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.

Stay Up-to-Date With OPSWAT!

Sign up today to receive the latest company updates, stories, event info, and more.