Cómo evitar las vulnerabilidades de asignación masiva en Node.js
28 de marzo de 2023
0 minutos de lecturaLa asignación masiva es una vulnerabilidad que permite a los atacantes aprovechar patrones de registros predecibles y ejecutar acciones no autorizadas. Suele ocurrir cuando no se filtran las propiedades al vincular datos proporcionados por el cliente con modelos de datos. Las vulnerabilidades de este tipo permiten que un atacante agregue objetos adicionales a las cargas útiles de las solicitudes POST y modifique propiedades que deberían ser inmutables.
Node.js es un entorno de servidor de código abierto que se puede usar en distintas plataformas para desarrollar aplicaciones web. Permite a los desarrolladores crear aplicaciones potentes y escalables con rapidez. En esencia, Node.js es una plataforma probada en batalla y lista para producción; los desafíos de seguridad de las aplicaciones incluyen la seguridad de la cadena de suministro asociada con los paquetes de terceros que se instalan para crearlas. A veces, crear una aplicación de Node.js implica usar miles de paquetes npm de código abierto del registro de npm, lo que la expone a diversos vectores de ataque.
Las vulnerabilidades de asignación masiva son comunes en las aplicaciones de Node.js que envían cargas útiles a la base de datos en solicitudes POST. Un atacante puede aprovechar estas vulnerabilidades para orquestar ataques de inyección SQL u otros ataques dirigidos a los datos almacenados en las bases de datos. También puede aprovechar una vulnerabilidad de asignación masiva para tomar el control total de un sistema o robar datos confidenciales, por lo que es fundamental defenderse de este tipo de ataque.
En este artículo se muestra una vulnerabilidad de asignación masiva en un proyecto de Node.js, cómo puede explotarla un atacante y cómo proteger una aplicación web.
Cómo eliminar las vulnerabilidades de asignación masiva en Node.js
En este ejemplo se usa Mongoose, una biblioteca de modelado de datos de objetos (ODM) para Node.js y MongoDB. Coordina los objetos del código con los objetos de MongoDB, valida esquemas y ayuda a administrar las relaciones entre los datos.
Con base en la información anterior, si quieres proteger una aplicación de Node.js con MongoDB contra la asignación masiva, debes hacerlo en Mongoose. A continuación, se muestra un ejemplo de cómo explotar una vulnerabilidad de asignación masiva en una aplicación de Node.js.
Requisitos previos
Para seguir este proyecto, necesitarás lo siguiente:
Tener Node.js instalado
Acceso a una base de datos MongoDB y una cadena de conexión válida. Este tutorial usa un entorno en la nube de MongoDB Atlas, pero puedes iniciar un servidor local de MongoDB y actualizar la cadena de conexión según corresponda.
Asignación masiva en Node.js
Para empezar con este proyecto, abre la terminal y ejecuta este comando:
A continuación, instala las dependencias:
Para procesar los datos del formulario anterior, necesitamos un archivo newUser.js en la carpeta models para gestionar el esquema del usuario.
Este modelo incluye todos los campos del formulario de registro. El campo adicional isAdmin, de tipo Boolean, define el rol del usuario. Si estableces este campo en true, crearás un usuario con privilegios de administrador; si lo estableces en false, crearás un usuario sin esos privilegios.
Crea un directorio llamado routes y agrega el archivo routes.js para gestionar los datos del usuario:
El código anterior captura los datos del formulario y luego los estructura con el modelo newUSer.
Este código tiene dos fallas que introducen una vulnerabilidad de asignación masiva. Primero, usa un nombre y un tipo comunes para un campo confidencial: isAdmin. Segundo, el modelo del usuario contiene un campo confidencial que no se usa. Para explotar la vulnerabilidad, iniciemos un servidor.
Crea un archivo llamado server.js en la carpeta raíz del proyecto y agrega el siguiente código. Reemplaza <APP_ID> por el valor de tu cadena de conexión:
Inicia el servidor con node server.js.
Un actor malicioso puede explotar esta vulnerabilidad creando una solicitud curl POST que incluya el campo isAdmin con el valor true, como se muestra en el código a continuación:
La solicitud anterior crea un usuario, Mike, con privilegios de administrador. Este usuario ahora puede usar sus credenciales para iniciar sesión en el sistema y hacer todo lo que puede hacer un administrador.
Cómo defenderte contra la asignación masiva
Podemos hacer tres ajustes para reforzar la protección de la aplicación contra los ataques de asignación masiva.
Crea modelos reducidos
Los modelos reducidos solo incluyen los datos que se espera que el usuario ingrese y excluyen los campos confidenciales.
A partir del ejemplo anterior, el primer paso y el más importante es eliminar el campo confidencial del modelo newUser, como se muestra a continuación:
Ahora, si los atacantes obtienen acceso al código fuente, no podrán ver el campo confidencial ni usarlo para orquestar un ataque. También debes evitar usar términos predecibles en la base de datos para definir el rol de un usuario, como isAdmin, admin y role, porque un atacante aún podría adivinarlos y lanzar un ataque.
Valida el esquema de los datos de entrada del usuario
Validación con underscore
Otra forma de proteger tu aplicación es especificar y limitar los campos que puede procesar una solicitud POST. Esto se conoce como lista de permitidos y utiliza la biblioteca underscore. Primero, instala la biblioteca:
Ve a la carpeta routes y reemplaza el código de route.js por este:
En el código anterior, usamos la función pick para especificar las variables que se extraen de una solicitud POST. Esto evita que un atacante use el campo confidencial isAdmin.
El problema de usar underscore es que la aplicación debe procesar los datos para determinar si son válidos. En otras palabras, no detecta los errores con suficiente anticipación.
Validación con Zod
Para validar esquemas de forma sólida, debes usar Zod. Esta herramienta valida el esquema y garantiza que los datos cumplan estrictamente con la estructura, el patrón y el tipo de datos especificados. Zod detecta los datos incompletos o incorrectos con suficiente anticipación para evitar errores en la aplicación. Además, puedes crear fácilmente un mensaje de error que indique al usuario qué datos de entrada causaron el error y de qué tipo es.
Este fragmento de código muestra cómo funciona Zod. Ten en cuenta que está escrito en TypeScript, por lo que requiere configuración adicional.
Si un usuario intenta ingresar datos que no coinciden con el esquema, recibirá un error que indica que se produjo un invalid_type_error. Si deja un campo vacío, recibirá un required_error.
Más ejemplos de vulnerabilidades de asignación masiva en ORM
Veamos otros tres ejemplos de vulnerabilidades de asignación masiva en soluciones ORM comunes.
Vulnerabilidad de asignación masiva en código de Sequelize
Veamos un ejemplo de código de Sequelize vulnerable a la asignación masiva. Este código define un modelo de usuario, crea un usuario y guarda sus datos en la base de datos:
Al crear un usuario nuevo, el controlador llama al modelo anterior, como se muestra a continuación:
El código anterior es vulnerable porque expone el campo confidencial is_verified. Un atacante puede crear una solicitud POST con el campo is_verified establecido en true y así omitir el paso de verificación.
Podemos eliminar la vulnerabilidad de asignación masiva de este código si quitamos el campo is_verified al crear el usuario. El código seguro se vería así:
Esto limita las variables aceptadas en la solicitud POST a username, email y password.
Vulnerabilidad de asignación masiva en código de Prisma
A continuación, se muestra un ejemplo de código de Prisma vulnerable a la asignación masiva que expone el campo confidencial Role.
Para agregar un usuario, creamos la ruta add_user.
Esta solicitud POST espera que el cuerpo incluya un objeto de usuario. El código anterior es vulnerable a los ataques de asignación masiva porque un hacker puede agregar el campo role con el valor ADMIN para obtener privilegios de administrador.
Podemos eliminar la vulnerabilidad del código anterior si excluimos el campo confidencial Role del modelo.
Vulnerabilidad de asignación masiva en código de MySQL
Este es un ejemplo de código de MySQL vulnerable a la asignación masiva que expone el campo confidencial isAdmin:
Este código agrega el usuario definido anteriormente:
El código anterior tiene una vulnerabilidad de asignación masiva porque expone un campo confidencial, isAdmin, y permite incluirlo en una solicitud POST. Un atacante puede crear una solicitud POST que establezca el campo isAdmin en true y crear así un usuario administrador.
Podemos corregir esta vulnerabilidad si excluimos el campo confidencial al crear el objeto user. Este es el código seguro:
El valor predeterminado false del campo isAdmin se puede establecer modificando la tabla con el siguiente comando:
Cómo mantener seguro tu código en Node.js
Node.js es seguro, pero a veces los paquetes necesarios para crear una aplicación web no lo son. En las aplicaciones de Node.js que envían solicitudes a una base de datos, la vulnerabilidad de asignación masiva es uno de los vectores de ataque más comunes. Permite que usuarios maliciosos realicen ataques de inyección SQL, que consisten en enviar solicitudes maliciosas a una base de datos.
Sin embargo, puedes prevenir fácilmente la asignación masiva en Node.js depurando los datos de entrada. Para hacerlo, evita exponer campos confidenciales, limita los campos aceptados o impide que se editen ciertos campos.
Si no implementas una validación sólida de los datos de entrada del usuario, expones el sistema a ataques como la contaminación de prototipos. Estos ataques pueden eludir mecanismos de validación débiles, como underscore. Por eso, asegúrate de usar siempre soluciones sólidas como Zod.
Consulta el ejemplo completo de código fuente en nuestro repositorio de GitHub y realiza tus propios experimentos de seguridad locales para aprender cómo evitar las vulnerabilidades de asignación masiva en Node.js.
Sigue las 10 mejores prácticas de seguridad de Snyk para Node.js para optimizar la seguridad de tu aplicación de Node.js.
Comienza con Capture the Flag
Aprende a resolver desafíos de Capture the Flag con nuestro taller virtual 101 a pedido.
