No SQL injection
Injection based attacks are top of the OWASP top 10.
We tend to think of Injection based attacks as affecting the traditional SQL style databases, and as such often developers ignore the dangers of injection on NoSQL style databases.
In-fact the MongoDB FAQ seems to reinforce this view1
As a client program assembles a query in MongoDB, it builds a BSON object, not a string. Thus traditional SQL injection attacks are not a problem. More details and some nuances are covered below.
Now while that is partially true, it sounds like someone throwing down a gauntlet, so lets see how NoSQL style DB can be exploited.
How NoSQL stores data
Rather than the traditional "table" based relational databases. NoSQL tends to store data in a more flattened file format.
There are several different ways that NoSQL can store data.
-
Document Based (ie Mongo)
Store data in documents similar to JSON (JavaScript Object Notation) objects. Each document contains pairs of fields and values. The values can typically be a variety of types including things like strings, numbers, booleans, arrays, or objects, and their structures typically align with objects developers are working with in code
-
Key Value (ie Redis)
Are a simpler type of database where each item contains keys and values. A value can typically only be retrieved by referencing its key, so learning how to query for a specific key-value pair is typically simple. Key-value databases are great for use cases where you need to store large amounts of data but you don’t need to perform complex queries to retrieve it
-
Wide-column (ie Cassandra)
Stores store data in tables, rows, and dynamic columns. Wide-column stores provide a lot of flexibility over relational databases because each row is not required to have the same columns. Many consider wide-column stores to be two-dimensional key-value databases.
-
Graph databases (ie Neo4j)
Store data in nodes and edges. Nodes typically store information about people, places, and things while edges store information about the relationships between the nodes.
Example
Lets take a quick look at an example, of a mongo style database
Consider a user table
Column | Type |
---|---|
id | Integer |
text | |
name | text |
password | text |
Rather than a table, a Document based NoSQL database stores the items as a set of JSON (like) rows.
Rows of the same type are referred to as a collection
{_id: <GUID> , name: "dan", email: "dan@evil.org", password: "swordfish"}
The Id parameter is a unique object Id, generated by the DB. Unlike most SQL style databases, this may not be sequential, and is usually based on a hash of the object itself
There are lots of advantages to this for the "modern web". Firstly, JSON style objects are supported by most programming languages, and are native to JavaScript. This means that it simplifies wiring programs that deal with large datasets.
Querying NoSQL
Rather than use SQL to query our database, we also take a different approach to querying the database. However, the principles are the same.
The basic syntax for a query is: db.COLLECTION.find()
(remember that collection is the equivalent of a SQL table)
Revisiting the Login Page
Remember our SQL Injection example to query the database.
$username = $_GET['username'] //Get username from request
$password = $_GET['password'] //Get password from request
$qry_string = "SELECT * FROM users WHERE username='".username."' AND password=".'password'"
$result = $conn -> query($sql);
if ($result->num_rows == 0){
//Redirect to fail
header( 'Location: fail.php');
}
The equivalent version using Mongo would look something like this.
$username = $_GET['username'] //Get username from request
$password = $_GET['password'] //Get password from request
$query = db.user.find_one({username: "$username", password: "$password"});
//Do something with the output of query
Advanced Queries
We can also filter the query using various filter statements2
Filter | SQL | Mongo |
---|---|---|
Equality | = | {key: value} |
Equality | = | {key:{eq:value}} |
Less Than | < | {key:{lt:value}} |
Greater Than | > | {key:{lt:value}} |
Not Equals | != | {key:{ne:value}} |
Compound Queries can also be built using the following
Action | Syntax |
---|---|
AND | { $and [item1, item2]} |
OR | { $or [item1, item2]} |
Warning
There is not much standardization here. Different databases may have slightly different syntax
For example, we could run the following query to select all users with the username dang
db.user.find({name:{eq:dang}})
Exploiting Mongo
These advanced queries are where the problem comes from. As usual, if we allow untrusted user input into our query we can change the way the query behaves.
Lets take our login page from before.
$query = db.user.find_one({username: "$username", password: "$password"});
Entering the expected input, performs the query as expected:
- username: "dan"
- password: "swordfish"
However, consider what happens if we enter the following
- username: "dan"
- password: [$ne]=""}}
This turns our query string into the equivalent of
db.user.fine_one({username: "dan", password {$ne: ""}})
Or any user who's username is "dan" and password isn't the empty string.
Advanced NoSQL Injection
You can find more examples of NoSQL style payloads on seclists