Parte: 4 — Seguridad de aplicaciones web · Fuente: OWASP WSTG / Bug Bounty Bootcamp (Vickie Li) ⏱️ Duración estimada: 90 min · Nivel: Intermedio
Entender que la inyección no es exclusiva de SQL: las bases NoSQL (MongoDB, etc.) tienen sus propios vectores. Aprenderás a explotar operadores de MongoDB y JavaScript del lado servidor para saltar autenticación y extraer datos.
⚠️ Ética: solo en laboratorios propios o autorizados. Estas técnicas modifican y exfiltran datos reales.
Al finalizar, el alumno podrá:
$ne, $gt, $regex, $where) como payloads.$regex.| # | Tema | Por qué importa |
|---|---|---|
| 1 | Modelo de datos NoSQL | Cambia la forma del ataque |
| 2 | Operadores de consulta MongoDB | Los payloads son operadores |
| 3 | Inyección vía JSON vs. query string | El formato altera el vector |
| 4 | Auth bypass con $ne/$gt |
Impacto directo |
| 5 | Blind NoSQLi con $regex |
Extracción carácter a carácter |
| 6 | $where y JS server-side |
Ejecución de lógica arbitraria |
| 7 | Defensa: validar tipos | Cierre del fallo |
Que una aplicación use MongoDB en lugar de SQL no la hace inmune a la inyección: el fallo raíz —mezclar entrada del usuario con la lógica de la consulta— es el mismo, solo cambia la forma. En las bases NoSQL documentales como MongoDB, las consultas no son cadenas de texto sino estructuras de datos (objetos JSON con operadores), y ahí está la vuelta de tuerca: la inyección no consiste en romper una cadena con una comilla, sino en cambiar el tipo o la estructura de lo que se envía para introducir operadores que la aplicación no esperaba. Entender esto es clave porque las defensas mentales del programador ("escapo las comillas") no aplican y dejan un hueco enorme.
MongoDB tiene operadores de consulta como $ne (distinto de), $gt (mayor que), $regex
(coincide con) o $where (evalúa JavaScript). El ataque clásico convierte un valor simple en
un objeto con uno de esos operadores. Imagina un login que consulta
{ user: entrada_user, password: entrada_pass }. Si la aplicación acepta JSON y no valida
tipos, el atacante envía { "user": "admin", "password": { "$ne": "" } }: el operador $ne ""
significa "cualquier contraseña distinta de vacío", es decir, coincide con cualquier
contraseña, y se entra como admin sin conocerla. Es el admin' -- de la clase 091 traducido
al mundo NoSQL, y funciona porque el desarrollador esperaba un texto y recibió un objeto.
Hay dos formas de colar esos operadores. La directa es una API que acepta JSON: ahí el
atacante controla la estructura por completo y puede anidar operadores a voluntad. La menos
obvia, y por eso peligrosa, es la query string: muchos frameworks (Express con
qs, PHP) parsean automáticamente una sintaxis como user[$ne]=x convirtiéndola en un
objeto { user: { $ne: "x" } } antes de que llegue a la consulta. Es decir, un parámetro de
URL de apariencia inofensiva se transforma en un operador de MongoDB sin que el atacante
necesite enviar JSON. Este comportamiento sorprende a muchos desarrolladores y es una fuente
recurrente de fallos.
Como en SQL, cuando no se ven los resultados existe la NoSQLi ciega: con $regex se
pregunta si un valor empieza por cierto patrón ({"password": {"$regex": "^a"}}) y, según
la aplicación responda a una coincidencia o no, se extrae el dato carácter a carácter,
exactamente como la inyección booleana de la clase 092. Y el operador $where —o
mapReduce— es el más grave, porque evalúa JavaScript en el servidor: si el atacante
controla lo que se ejecuta ahí, puede pasar de leer datos a ejecutar lógica arbitraria, un
escalón hacia el RCE.
La defensa tiene un acento distinto al de SQL y conviene subrayarlo: además de no construir
consultas con entrada sin validar, lo decisivo es validar los tipos. Si un campo debe ser
una cadena, hay que rechazar explícitamente que llegue un objeto —comprobar
typeof entrada === 'string', usar esquemas de validación como los de Mongoose, y sanear los
operadores $—. En NoSQL, la comprobación de tipos no es una buena práctica opcional: es la
barrera que impide el bypass más común.
$ne: "not equal". Característica: {"$ne": null} casi siempre es verdadero, ideal para bypass.$regex: coincidencia por expresión regular. Característica: permite inferir datos carácter a carácter (blind).$where: ejecuta JavaScript en el servidor Mongo. Característica: potente y peligroso; puede permitir DoS o extracción.{"$ne":""} donde se espera un string. Característica: posible cuando el backend no valida tipos.| Término | Definición concisa |
|---|---|
| NoSQL | Bases no relacionales; MongoDB es documental (JSON) |
| Inyección NoSQL | Alterar la estructura o el tipo de la consulta con operadores |
| Operador de consulta | $ne, $gt, $regex, $where de MongoDB |
$ne |
"Distinto de"; con "" coincide con cualquier valor |
Auth bypass con $ne |
password: {$ne: ""} entra sin conocer la contraseña |
| Confusión de tipo | Enviar un objeto donde se espera una cadena |
| Vía JSON | La API acepta JSON y el atacante controla la estructura |
| Vía query string | user[$ne]=x se parsea a objeto automáticamente |
| Parseo de parámetros | Frameworks que convierten la query string en objetos |
| NoSQLi ciega | Extraer datos con $regex carácter a carácter |
$regex |
Coincidencia por patrón; base de la inyección ciega |
$where |
Evalúa JavaScript en el servidor; riesgo de RCE |
| Validación de tipos | Rechazar objetos donde se espera texto; la defensa clave |
| Esquema de validación | Mongoose u otros que fuerzan el tipo de cada campo |
# NodeGoat como lab NoSQL en Node/Mongo
git clone https://github.com/OWASP/NodeGoat && cd NodeGoat && docker compose up
⚠️ Solo en tu laboratorio.
{"username":"x","password":"y"}).{"username":"admin","password":{"$ne":""}}
username[$ne]=&password[$ne]=.$regex para adivinar la contraseña:{"username":"admin","password":{"$regex":"^a"}}
^a, ^ab, ...) según la respuesta de login para reconstruir el valor.$where, prueba una condición JS y evalúa el riesgo (sin causar DoS).$regex blind.{"$gt":""} también funciona como bypass.typeof === 'string').$where y por qué está desaconsejado.Consigue un bypass de autenticación en un lab NoSQL (NodeGoat/Juice Shop) y luego extrae parcialmente una credencial con NoSQLi ciega por $regex.
Criterio de aceptación: demuestras el login sin conocer la contraseña y recuperas al menos los primeros caracteres del valor real mediante $regex, documentando los payloads.
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
| El operador no se interpreta | El backend recibe string, no objeto; ajusta Content-Type/formato |
$where bloqueado |
Deshabilitado en el servidor; usa otros operadores |
| Regex demasiado lento | Usa anclas ^ y búsqueda incremental |
| Bypass no funciona en query string | La app parsea distinto; prueba notación param[$ne] |
| Falsos positivos | Confirma con dos operadores distintos |
❓ ¿Por qué el JSON facilita NoSQLi? Porque un campo que debería ser string puede convertirse en un objeto con operadores si el servidor no valida el tipo.
❓ ¿MongoDB es inseguro por diseño? No; el problema es el código que pasa input sin validar tipos ni sanitizar. Con validación estricta no hay NoSQLi.
❓ ¿Sirve sqlmap para NoSQL? No. Para NoSQL existe NoSQLMap y payloads manuales; los conceptos son análogos pero la sintaxis difiere.