The NoSQL injection vulnerability can be used by a malicious actor to access and modify sensitive data, including usernames, email addresses, password hashes and login tokens. Chained with other vulnerabilities it can lead to a full site takeover.
What is a NoSQL database?
NoSQL (non-SQL or non-relational) databases have existed for a while now, but the term gained wider popularity in early 2009. There are different types and examples of NoSQL databases, some are listed here:
Key-Value | Key-Document | Column-Family | Graph |
---|---|---|---|
Redis | MongoDB | Cassandra | Neo4j |
MemcacheDB | CouchDB | HBase | OrientDB |
The main ways NoSQL databases differ from more traditional SQL databases are:
- No structured query language
- Dynamic schema
- Horizontal scalability
- Fields can be added to documents on the fly without affecting the rest of the data
By far the most widely used NoSQL database is MongoDB. 1 For this reason, examples in this post are also based on MongoDB.
Finding the injection
Find where the application interacts with the database server. Some examples include:
- Authentication forms
- Filtering or search forms
- Headers and cookies
After enumerating the input fields that might be used in a database query, the tester should attempt to modify the values in order to trigger an error or unexpected behaviour.
If there is no sanitation on the input fields, some characters can break the query, e.g ' " \ ; { } ( )
For automated detection, NoSQLMap can sometimes be leveraged to catch the low-hanging fruit, although more advanced cases almost always require manual testing, static analysis and code review.
JavaScript injection in querys
Although the MongoDB API usually expects BSON (binary JSON) data, some JSON and unserialized JavaScript expressions are permitted. For MongoDB, the most common such operator is $where
which filters the results similarly to WHERE in SQL. Others include mapReduce and group.
Let’s have a look at the following code:
Posts.find({ $where: `this.hidden == false && this.author == '${req.query.author}'` }, (err, posts) => { ... });
Normally, this query would return posts published by the specified author. However, by requesting GET /posts?author='+||+''=='
, the JavaScript query turns into this:
this.hidden == false && this.author == '' || ''==''
This would return all posts by all authors.
A Denial-of-Service attack is also possible by requesting GET /posts?author=';while(1);'
A longer list of malicious inputs can be found on GitHub.
Tautology
If a form is submitted as JSON, changing a field value to a MongoDB query operator might be able to create a tautology - an expression which is always true. For example, consider the following vulnerable code which searches for a user by the username and password specified in the submitted POST request:
User.findOne({ username: req.body.username, password: req.body.password }, (err, user) => { ... });
If the request body is encoded as JSON and the password is changed from a string to a query expression that evaluates to true, the password check becomes irrelevant and an attacker can authenticate themself as any user. For that, we are going to use the $ne
query selector, which finds the documents where the value of the field is not equal to the specified value.
{
"username": "admin",
"password": {"$ne": ""}
}
The same can be done with the more common encoding for HTML forms, the urlencoding method. The NodeJS module qs
parses the following query string to the same JSON:
username=admin&password[$ne]=
Mitigation
- Don’t build queries from strings, use safe APIs and prepared statements.
- Validate input to detect malicious values, keeping in mind to also validate input types against expected types.
- To minimize the potential damage of a successful injection attack, do not assign DBA or admin type access rights to your application accounts.
From MongoDB documentation:2
You can express most queries in MongoDB without JavaScript and for queries that require JavaScript, you can mix JavaScript and non-JavaScript in a single query. Place all the user-supplied fields directly in a BSON field and pass JavaScript code to the $where field.
You can disable all server-side execution of JavaScript in MongoDB by passing the
--noscripting
option on the command line or settingsecurity.javascriptEnabled
in a configuration file.
Conclusion
NoSQL does not equal no injections - NoSQL databases suffer from the same security risks as their SQL counterparts. Using even the most secure data store does not prevent injection attacks if the application has not been developed with secure coding practices in mind.