Why is this an issue?
NoSQL injections occur when an application retrieves untrusted data and inserts it into a database query without sanitizing it first.
What is the potential impact?
In the context of a web application that is vulnerable to NoSQL injection:
After discovering the injection point, attackers insert data into
the vulnerable field to execute malicious commands in the affected databases.
Below are some real-world scenarios that illustrate some impacts of an attacker exploiting the vulnerability.
Identity spoofing and data leakage
In the context of simple query logic breakouts, a malicious database query enables privilege escalation or direct data leakage from one or more
databases.
This threat is the most widespread impact.
Data deletion and denial of service
The malicious query makes it possible for the attacker to delete data in the affected databases.
This threat is particularly insidious if the
attacked organization does not maintain a disaster recovery plan (DRP) as missing data can disrupt the regular operations of an organization.
Chaining NoSQL injections with other vulnerabilities
Attackers who exploit NoSQL injections rely on other vulnerabilities to maximize their profits.
Most of the time, organizations overlook some
defense in depth measures because they assume attackers cannot reach certain points in the infrastructure. This misbehavior can lead to multiple
attacks with great impact:
- When secrets are stored unencrypted in databases: Secrets can be exfiltrated and lead to compromise of other components.
- If server-side OS and/or database permissions are misconfigured, injection can lead to remote code execution (RCE).
How to fix it in MongoDB
Code examples
The following code is vulnerable to a NoSQL injection because the database query is built using untrusted JavaScript objects that are extracted
from user inputs.
Here the application assumes the user-submitted parameters are always strings, while they might contain more complex structures. An array or
dictionary input might tamper with the expected query behavior.
Noncompliant code example
const { MongoClient } = require('mongodb');
function (req, res) {
let query = { user: req.query.user, city: req.query.city };
MongoClient.connect(url, (err, db) => {
db.collection("users")
.find(query) // Noncompliant
.toArray((err, docs) => { });
});
}
Compliant solution
const { MongoClient } = require('mongodb');
function (req, res) {
let query = { user: req.query.user.toString(), city: req.query.city.toString() };
MongoClient.connect(url, (err, db) => {
db.collection("users")
.find(query)
.toArray((err, docs) => { });
});
}
How does this work?
Use only plain string values
With MongoDB, NoSQL injection can arise when attackers are able to inject objects in the query instead of plain string values. For example, using
the object { $ne: "" }
in a field of a find
query, will return every entry where the field is not empty.
Some JavaScript application servers enable "extended" syntax that serializes URL query parameters into JavaScript objects or arrays. This allows
attackers to control all the fields of an object. In express.js, this "extended" syntax is enabled by default.
Before using any untrusted value in a MongoDB query, make sure it is a plain string and not a JavaScript object or an array.
In some cases, this will not be enough to protect against all attacks and strict validation needs to be applied (see the "Pitfalls" section)
Pitfalls
Code execution
When untrusted data is used within query operators such as $where
, $accumulator
, or $function
it usually
results in JavaScript code execution vulnerabilities.
Therefore, untrusted values should not be used inside these query operators unless they are properly validated.
For more information about MongoDB code execution vulnerabilities, see rule S5334.
How to fix it in Mongoose
Code examples
The following code is vulnerable to a NoSQL injection because the database query is built using untrusted JavaScript objects that are extracted
from user inputs.
Here the application assumes the user-submitted parameters are always strings, while they might contain more complex structures. An array or
dictionary input might tamper with the expected query behavior.
Noncompliant code example
const mongoose = require('mongoose');
function(req, res) {
let query = { user: req.query.user, city: req.query.city };
const userModel = mongoose.model('User', userSchema);
userModel.find(query, (err, users) => {}); // Noncompliant
}
Compliant solution
const mongoose = require('mongoose');
function (req, res) {
let query = { user: req.query.user.toString(), city: req.query.city.toString() };
const userModel = mongoose.model('User', userSchema);
userModel.find(query, (err, users) => {});
}
How does this work?
Use only plain string values
With MongoDB, NoSQL injection can arise when attackers are able to inject objects in the query instead of plain string values. For example, using
the object { $ne: "" }
in a field of a find
query, will return every entry where the field is not empty.
Some JavaScript application servers enable "extended" syntax that serializes URL query parameters into JavaScript objects or arrays. This allows
attackers to control all the fields of an object. In express.js, this "extended" syntax is enabled by default.
Before using any untrusted value in a MongoDB query, make sure it is a plain string and not a JavaScript object or an array.
In some cases, this will not be enough to protect against all attacks and strict validation needs to be applied (see the "Pitfalls" section)
Pitfalls
Code execution
When untrusted data is used within query operators such as $where
, $accumulator
, or $function
it usually
results in JavaScript code execution vulnerabilities.
Therefore, untrusted values should not be used inside these query operators unless they are properly validated.
For more information about MongoDB code execution vulnerabilities, see rule S5334.
Resources
Articles & blog posts
Standards