How to scan for MongoDB injection vulnerabilities – and how to fix them
NoSQL injection attacks against MongoDB databases are a major threat to full-stack JavaScript applications. Invicti security engineer Özgür Dinç shows how to find MongoDB injection vulnerabilities with Invicti’s vulnerability scanner – and how to fix them.
Your Information will be kept private.
Your Information will be kept private.
As part of our recent work on detecting NoSQL injection vulnerabilities with Invicti, we developed and added security checks for MongoDB injection. This post shows how to scan for MongoDB injection vulnerabilities with Invicti, how to analyze vulnerability reports, and how to fix reported vulnerabilities.
What are MongoDB injection vulnerabilities?
MongoDB is by far the most popular NoSQL database and currently the fifth most popular database engine overall (according to the DB-Engines ranking). Used alongside Express, Angular, and Node.js, it is the foundation of the MEAN software stack for building full-stack JavaScript web applications. Technically, it is a document database type that stores its documents in JSON format.
Similar to traditional SQL injection, MongoDB injection attacks rely on unsanitized user input and are performed by entering payloads that modify MongoDB queries. There are two main types of MongoDB injections: injecting MongoDB keywords into a vulnerable PHP application and injecting JavaScript into MongoDB queries. You can find an overview of MongoDB injection methods in a previous blog post on NoSQL injection, but here is a quick recap.
MongoDB injection into PHP applications
MongoDB injection in PHP applications exploits the way that PHP converts associative arrays to inject MongoDB operators. As a quick example with the $ne
(not equals) operator, let’s say we have a PHP application that receives a parameter like name=test
. PHP converts this to the array {"name":"test"}
. If we can inject the parameter as name["$ne"]=test
, PHP will convert the whole thing to {"name": {"$ne":test}}
. When sent to MongoDB as a query, this will now return all records whose name is not equal to test
(because of the $ne
operator).
JavaScript injection into MongoDB queries
MongoDB allows JavaScript expressions in some queries. When unsanitized user input is passed directly to the $where
query operator, attackers may be able to inject malicious JavaScript into database queries. Only a limited subset of JavaScript properties and functions can be injected, but this is quite enough for a useful attack – for example, you can access the sleep()
function for time-based attacks.
Preparing the scanner to test for MongoDB injection vulnerabilities
Because MongoDB is the back-end database for so many modern web applications, NoSQL injection can now be just as dangerous as SQL injection. To help organizations find and remediate MongoDB injection vulnerabilities, we have added fully automated security checks for these vulnerabilities to Invicti products. We will go through the following steps to demonstrate how you can easily identify and fix such security defects:
- Set up a vulnerable application for testing (NodeGoat)
- Run a scan with Invicti Standard
- Analyze the vulnerability report
- Track down the vulnerability in application code
- Remediate the vulnerability and follow best practices to avoid similar issues in the future
Setting up the test environment
As our demo environment, we can use NodeGoat – a vulnerable Node.js application from OWASP that uses MongoDB. You can find more information about the NodeGoat project at the OWASP site. Setting up the application is a matter of issuing a few docker-compose
commands:
git clone https://github.com/OWASP/NodeGoat.git
docker-compose build
docker-compose up
If all goes well, after executing these commands, the NodeGoat application should be accessible at http://[YOUR-IP-ADDRESS]:4000
.
After accessing the application, we can create a new user on the first page:
The application is now ready to scan. To set up and run the scan, we will use Invicti Standard. For detailed information about scanning, see our support page on MongoDB injection checks.
MongoDB injection checks are available in Invicti Standard versions later than 6.8.0.38168 and in Invicti Enterprise on-demand since October 2022.
Creating a scan profile
For this simple test site, all we need to do to create a scan profile is to set the target URL and enable form authentication:
Selecting the scan policy
The next step is to select the MongoDB injection security checks we want to run. For blind MongoDB injections, select MongoDB Injection (Blind) under the NoSQL Injection security check group:
To check for error-based MongoDB injections, select MongoDB Injection (Error-Based) under the NoSQL Injection security check group:
Finding and fixing MongoDB injection vulnerabilities
With the setup done, now you just hit the Scan button and examine the results. As a dynamic application security testing (DAST) solution, Invicti probes the application from the outside by sending requests that include test attack payloads. Where possible, the scanner attempts to safely exploit vulnerabilities to prove they are real. We will look at a MongoDB injection vulnerability report, find the insecure application code, remediate the issue, and retest to make sure it is fixed.
Understanding scan results
The NodeGoat application has a deliberate MongoDB injection vulnerability under the allocations directory in the threshold
parameter. Depending on which MongoDB injection security check is selected, Invicti will report this vulnerability either as a blind MongoDB injection or an error-based MongoDB injection. For blind MongoDB injection, Invicti will also attempt to confirm the vulnerability by extracting the version number of the MongoDB instance used by the application. When successful, this will be reported in the Proof of Exploit section of the vulnerability report:
When detected using checks for error-based MongoDB injections, the same vulnerability is reported as follows:
Analyzing the vulnerability
Now that we know we have a vulnerability, we need to find exactly where it is, how the attack payload gets through, and how we can fix the issue. In the expected usage of the vulnerable page, the user enters an integer and the application displays all of the user’s stock that is currently valued above that threshold. Here is an example of output for the current user:
After scanning the application with Invicti, we now know that the Allocations page is vulnerable to NoSQL injections into the threshold
parameter. We can confirm this manually by entering this simple payload that should list asset allocations for all users:
threshold=1'; return '1' == '1
As you can see below, the injection works and we can now see stock data for all application users in the output, not only our test user:
In the source code of the NodeGoat application, this vulnerability is introduced in the allocations-dao.js file referenced by allocations.js in the app/routes folder. Let’s go through the process of isolating this specific file based on the vulnerability report.
We can start by checking server.js in the root folder. This file is where the NodeGoat application starts and where we can see which config file is used and which files are referenced. We can quickly discover that the ./app/routes folder is referenced as routes on line 16, so we can continue checking app/routes to find the vulnerable code:
Below, you can see the structure of the ./app/routes folder:
The index.js file defines which URL is routed to which file. In line 5 of index.js below, we can see the allocations.js file is assigned to AllocationsHandler
. This line is important and worth checking because, from the vulnerability reports, we know that the vulnerability is under http://[ipaddress]:4000/allocations/:id:
.
Looking at the allocations.js file under ./app/routes, we can see that it takes userid
and threshold
as parameters and then calls the getByUserIdAndThreshold()
method from the file allocations-dao.js in the ./app/data folder:
Finally, in the allocations-dao.js file, we can search for the getByUserIdAndThreshold()
method to see what it does. Immediately, you can see that the MongoDB query uses the $where
operator, which is an instant red flag (as explained earlier). Unsurprisingly, we can also see that the input parameters are directly used in the query without any validation or sanitization:
Now that we’ve found the root cause of the vulnerability, let’s see how to fix the issue and avoid it in the future by following best practices.
How to fix MongoDB injection vulnerabilities
If you look at the vulnerability report delivered by Invicti, you will see the following remediation guidance for MongoDB injection vulnerabilities:
- Sanitize and strictly check the type of user-supplied input.
- Avoid using
$where
,mapReduce
, or$group
in combination with user input. - If possible, set the
javascriptEnabled
flag tofalse
in mongod.conf. - Use the most recent version of MongoDB.
Let’s go through these recommendations to fix the vulnerability and then retest to see if it is truly fixed.
Sanitizing user-controlled inputs
All injection attacks are made possible by unsanitized inputs, so this is where all remediation should start. Since the NodeGoat application was created by OWASP for educational purposes, it already includes a remediation method. Looking at the allocations-dao.js file in the screenshot below, you can see the fix suggested in the comment (in blue):
The suggested solution focuses on input sanitization and validation by casting the threshold
parameter to an integer and then checking if it is between 0 and 99. See the documentation of the parseInt() function to learn more about integer casting. To apply this fix, simply uncomment it while commenting out the vulnerable code. The code should now look like this (note the uncommented section between lines 70 and 75):
Avoiding insecure MongoDB operators and commands
Even with validated input, you can see that the application still uses the $where
operator in the query, which is not a secure practice when working with user-controlled inputs. To remedy this, we can write a better and more secure query. After a little search, we can recreate the query without using the $where
operator. In line 74 of the allocations-dao.js file below, you can see the new query:
Disabling JavaScript in MongoDB configuration
If you know your application does not use or need JavaScript in MongoDB queries, you should disable JavaScript in mongod.conf using MongoDB configuration options. The javascriptEnabled
option is true by default. When set to false, it disables all operations that perform server-side JavaScript execution, such as $where
, mapReduce
, $accumulator
, and $function
. In a default installation, mongod.conf is placed in the /etc folder. To make the change, simply append javascriptEnabled: false
to the security
section of the configuration file and restart the mongod service.
Always using the most recent version
As a general security best practice, you should always use the latest secure versions of all software wherever possible. Especially for MongoDB, which had some serious security issues in early versions, using the latest version is important to prevent your application from being vulnerable to already published vulnerabilities. See the MongoDB version history to learn how the database has evolved.
Retesting to make sure the vulnerability is fixed
After applying all the remediation steps, we can be pretty confident that the vulnerability is fixed – but testing is the only way to be sure. To check if we resolved the vulnerability, we can simply scan the application again with Invicti Standard using the same policy and profile. As you can see, this time no results are returned for MongoDB injection security checks, so the vulnerability was not detected:
Conclusion
MongoDB databases are a critical part of the modern web, yet automated security testing tools for NoSQL databases are still far less mature than for relational databases. To address this, Invicti solutions can now detect when applications are vulnerable to MongoDB injections. Depending on the type of security check, the scanner can automatically confirm many such vulnerabilities by safely extracting version information from the MongoDB database.
Successful NoSQL injection attacks can be just as dangerous as SQL injection, potentially allowing attackers to read, change, or delete data and collect sensitive information about the system. Knowing the right secure development practices for working with MongoDB is as important as regularly testing your applications for security issues. Hopefully, this post has shown that there are many ways to fix MongoDB injection vulnerabilities and avoid them in the future – and that with the right tools, you can test and retest your applications as often as you need.