Skip to main content

5 fragmentos de código de seguridad de Node.js que todo desarrollador backend debería conocer

Escrito por
feature nodejs security snippets

28 de febrero de 2024

0 minutos de lectura

Como desarrolladores backend, tenemos la importante responsabilidad de garantizar la seguridad de nuestras aplicaciones. Node.js no está exento de esta responsabilidad y su creciente popularidad lo convierte en un objetivo atractivo para los hackers, por lo que es fundamental seguir las mejores prácticas de seguridad al trabajar con Node.js.

En esta publicación del blog, exploraremos algunos fragmentos esenciales de código de seguridad de Node.js que todo desarrollador backend debería conocer en 2024. Las vulnerabilidades de software de Node.js pueden representar una amenaza considerable para cualquier aplicación. Pueden provocar accesos no autorizados, filtraciones de datos y, en el peor de los casos, el compromiso total del sistema. Por eso, seguir las mejores prácticas de seguridad no es solo una recomendación, sino un requisito.

En el contexto de Node.js, esto significa asegurarte de que tu código no exponga vulnerabilidades que puedan aprovecharse. Significa sanear los datos de entrada de los usuarios para prevenir ataques de inyección, tratar las contraseñas como datos confidenciales y administrar las dependencias para evitar vulnerabilidades de terceros, entre otras prácticas.

Revisaremos los siguientes conceptos de seguridad de Node.js y sus fragmentos de código correspondientes, considerando su eficacia para prevenir vulnerabilidades comunes y lo accesibles que son para los desarrolladores, sin necesidad de contar con conocimientos adicionales de seguridad:

  1. Usa el modelo de permisos de Node.js para restringir el acceso a los recursos durante la ejecución

  2. Implementa la validación de entradas con un esquema JSON de Fastify

  3. Protege el hash de contraseñas con Bcrypt

  4. Previene ataques de inyección SQL con Knex.js

  5. Implementa la limitación de frecuencia con fastify-rate-limit

1. Usa el modelo de permisos de Node.js para restringir el acceso a los recursos durante la ejecución

El modelo de permisos de Node.js puede desempeñar un papel fundamental en la protección de tus aplicaciones. Es una parte clave del modelo de seguridad básico de Node.js y ayuda a garantizar que tus aplicaciones estén protegidas contra ataques y actividades maliciosas. De manera similar a la promesa de seguridad que Deno ofreció con las restricciones de recursos centradas en los procesos, Node.js ahora cuenta, hasta cierto punto, con un modelo de permisos similar.

Imagina que una aplicación de Node.js necesita convertir archivos PDF en imágenes PNG. Podríamos usar el paquete npm pdf-image para realizar esta tarea. El paquete pdf-image usa procesos secundarios para llevar a cabo la conversión. Para habilitar esta función, debemos usar la opción --allow-child-process en el entorno de ejecución de Node.js (que proporciona el modelo de permisos).

A continuación, se muestra un fragmento de código que lo demuestra:

const { PDFImage } = require('pdf-image');
const path = require('path');

const pdfPath = path.resolve(__dirname, 'sample.pdf');
const pdfImage = new PDFImage(pdfPath, {
  convertOptions: {
    '-density': '300',
    '-quality': '80'
  },
  combinedImage: true
});
pdfImage.convertFile().then(() => {
  console.log('PDF converted to PNG successfully');
}).catch((err) => {
  console.error(`Failed to convert PDF to PNG: ${err}`);
});

En este fragmento, creamos una nueva instancia de PDFImage con la ruta a nuestro archivo PDF y algunas opciones de conversión. Luego, llamamos a convertFile() para convertir el PDF en una imagen PNG.

Si habilitas el modelo experimental de permisos en Node.js, tendrás que ejecutar explícitamente el entorno de ejecución de Node.js con la opción de línea de comandos --allow-child-process mencionada anteriormente, ya que internamente la biblioteca pdf-image inicia un proceso secundario para convertir estos archivos PDF.

Aunque el modelo de permisos de Node.js ofrece una excelente forma de restringir el acceso a los recursos del sistema, también es fundamental conocer las posibles vulnerabilidades de seguridad de los paquetes que usamos. Aquí es donde entra en juego la extensión de Snyk para Visual Studio Code. La extensión de Snyk puede detectar código inseguro y dependencias vulnerables en una aplicación de Node.js. Por ejemplo, se sabe que el paquete pdf-image mencionado anteriormente es vulnerable a la inyección de comandos debido al uso de procesos secundarios. Esta vulnerabilidad se conoce desde 2018 y, lamentablemente, todavía no hay una solución disponible.

Esto resalta la importancia de usar herramientas como Snyk para mantenerte al tanto de las vulnerabilidades de seguridad en los paquetes de los que dependemos. También nos recuerda que debemos tener cuidado al usar ciertas funciones, como los procesos secundarios, que pueden representar riesgos de seguridad si no se manejan correctamente.

Como desarrolladores de Node.js, tenemos la responsabilidad de asegurarnos de que el código que escribimos no solo funcione, sino que también sea seguro. Una forma clave de mejorar la seguridad de una aplicación de Node.js es usar el modelo de permisos integrado para restringir el acceso a los recursos del sistema durante la ejecución. Si sabes que tu aplicación de Node.js no necesita capacidades de procesos secundarios, no habilites este recurso para evitar ampliar la superficie de ataque.

Empieza a proteger tus aplicaciones JavaScript

Encuentra y corrige vulnerabilidades de JavaScript gratis con Snyk. 

No necesitas tarjeta de crédito.

O regístrate con Azure AD Docker ID Bitbucket

Al usar Snyk, aceptas cumplir nuestras políticas, incluidos nuestros Términos del servicio y nuestra Política de privacidad.

Preguntas frecuentes sobre el modelo de permisos de Node.js

¿Qué amenazas de seguridad previene el modelo de permisos de Node.js?

El modelo de permisos de Node.js está diseñado para prevenir diversas amenazas de seguridad, como el acceso no autorizado a archivos, los ataques de inyección de comandos y la escalación de privilegios.

¿Puedo personalizar el modelo de permisos de Node.js?

Sí, algunos de los recursos que se rigen por el modelo de permisos de Node.js son muy personalizables. Por ejemplo, al limitar el acceso a los recursos de archivos, puedes especificar distintos archivos o rutas de archivo, como --allow-fs-read=*.

¿El modelo de permisos de Node.js es suficiente para proteger mis aplicaciones?

Aunque el modelo de permisos de Node.js es una parte fundamental de la seguridad de las aplicaciones, por sí solo no es suficiente. También debes adoptar prácticas de programación segura, auditar periódicamente tu código para detectar vulnerabilidades y revisar si tus dependencias de terceros tienen vulnerabilidades. Además, usa herramientas como Snyk para identificar y corregir posibles problemas de seguridad.

2. Implementa la validación de entradas con un esquema JSON de Fastify

La validación de entradas es un aspecto fundamental del desarrollo backend. Si ya creaste API con Express, Fastify u otros frameworks, probablemente ya conozcas su importancia. Es una práctica de seguridad que garantiza que solo ingresen datos con el formato correcto a tu sistema y, de ese modo, previene posibles riesgos de seguridad. Una forma de implementar la validación de entradas en tus aplicaciones de Node.js es usar el esquema de Fastify en tus aplicaciones web de Fastify.

Enfoque basado en esquemas de Fastify

Fastify usa un enfoque basado en esquemas para validar las entradas. Aunque no es obligatorio, se recomienda usar el esquema JSON para validar las rutas y serializar las salidas. El esquema JSON establece un contrato para tus datos y detalla el formato esperado, los tipos de datos, los campos obligatorios y otras restricciones. Puedes pensar en el esquema JSON de una ruta de Fastify como el uso de TypeScript para garantizar un tipado estricto en tu código durante el proceso de compilación.

Imagina una aplicación sencilla de Fastify en la que creamos un nuevo usuario. Podemos usar un esquema de Fastify para validar los datos de entrada.

const fastify = require('fastify')({ logger: true });

const UserSchema = {
  body: {
    type: 'object',
    properties: {
      name: { type: 'string' },
      email: { type: 'string', format: 'email' },
      password: { type: 'string', minLength: 8 }
    },
    required: ['name', 'email', 'password']
  }
};

// User creation route
fastify.post('/users', { schema: UserSchema }, async (request, reply) => {
  // Example user creation logic
  // In a real application, you would replace this with actual database logic
  const user = request.body;

  // Simulating user creation
  console.log("Creating user:", user);

  // Responding with the created user (in real applications, never send the password back)
  return reply
    .code(201)
    .send({ success: true, message: "User created", user: { name: user.name, email: user.email } });
});

// Server startup
const start = async () => {
  try {
    await fastify.listen({ port: 6000, host: 'localhost' });
    console.log(`Server running at http://localhost:3000/`);
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();

En el código anterior, definimos un UserSchema que requiere un nombre, un correo electrónico y una contraseña. El correo debe tener un formato válido y la contraseña debe tener al menos 8 caracteres.

No validar las entradas puede exponer tu aplicación a varios riesgos de seguridad, como la falsificación de solicitudes del lado del servidor (SSRF) o los ataques de contaminación de parámetros HTTP. Los ataques SSRF pueden engañar al servidor para que realice solicitudes no autorizadas, lo que podría exponer datos; por su parte, la contaminación de parámetros HTTP puede manipular o corromper las solicitudes y provocar comportamientos inesperados.

Herramientas como Snyk pueden ayudar a descubrir estas vulnerabilidades al analizar tu código en busca de fallas de seguridad. También pueden ofrecer recomendaciones de corrección para ayudarte a resolverlas y mejorar la seguridad general de tu aplicación.

Preguntas frecuentes sobre la validación de entradas con Fastify

¿Es necesaria la validación de entradas?

Sí, la validación de entradas es una práctica de seguridad fundamental que evita que datos con formato incorrecto ingresen a tu sistema.

¿Puedo usar otras bibliotecas de validación con Fastify?

Sí, Fastify es flexible y te permite usar otras bibliotecas de validación, como Joi, yup o ajv.

¿La validación de esquemas de Fastify afecta el rendimiento?

Fastify está diseñado para ofrecer un alto rendimiento y la validación de esquemas tiene un impacto mínimo en él.

4. ¿Qué es un esquema JSON de Fastify?

Los esquemas de Fastify se utilizan para validar los datos de las solicitudes y respuestas en tus rutas, lo que garantiza que solo se procesen los datos que cumplen con los criterios especificados. Esto reduce el riesgo de procesar datos no válidos que podrían provocar vulnerabilidades de seguridad o errores en la aplicación.

3. Protege el hash de contraseñas con Bcrypt

Almacenar de forma segura las contraseñas de los usuarios es fundamental en el desarrollo de software. En caso de una filtración de datos, debes asegurarte de que la información de las contraseñas de los usuarios no se pueda descifrar fácilmente. Aquí es donde entra en juego el hashing de contraseñas. El hashing es el proceso de convertir una clave determinada en otro valor. Se usa una función hash para generar el nuevo valor, que idealmente debería producir una salida única (o código hash) para cada valor de entrada único.

Si decides implementar la autenticación por tu cuenta en lugar de recurrir a un servicio o a otra biblioteca, es fundamental que entiendas que la forma en que proteges las contraseñas puede afectar directamente la seguridad general de tu aplicación.

Introducción a Bcrypt para el hashing de contraseñas en Node.js

Bcrypt es un algoritmo y un paquete npm que ofrece una función de hashing de contraseñas considerada muy segura. Incorpora una sal (datos aleatorios) para proteger contra los ataques de tablas arcoíris y permite configurar un factor de trabajo, que determina cuánto procesamiento de CPU requiere el hashing. Esta característica resulta útil para prevenir ataques de fuerza bruta.

Veamos un caso de uso sencillo:

const bcrypt = require('bcrypt');
const saltRounds = 10;
const myPlaintextPassword = 'my_password';

// Define an async function
async function hashPassword(plaintextPassword) {
  try {
    const hash = await bcrypt.hash(plaintextPassword, saltRounds);
    // Store hash in your password DB.
    console.log(hash); // Example of how to use the hash
  } catch (err) {
    // Handle error
    console.error(err);
  }
}

// Call the async function
hashPassword(myPlaintextPassword);

El parámetro saltRounds determina la complejidad del proceso de hashing. Cuanto mayor sea el valor, más tiempo tardará el proceso, lo que puede ayudar a proteger contra los ataques de fuerza bruta. La función hash agrega una sal y aplica automáticamente el hashing a la contraseña en texto plano. Luego, puedes almacenar el hash resultante en tu base de datos.

Preguntas frecuentes sobre el hashing de contraseñas y la administración de la autenticación en Node.js

¿Cuál es una buena longitud de sal para usar con Bcrypt?

Bcrypt genera automáticamente una sal de 16 bytes.

¿Con qué frecuencia debo actualizar mi algoritmo o estrategia de hashing?

Si hay avances importantes en la tecnología de hashing o se descubre una vulnerabilidad en tu algoritmo actual, es recomendable actualizar tu estrategia de hashing. Node.js incluye compatibilidad integrada con el algoritmo scrypt como parte del módulo principal node:crypto.

¿Qué es el factor de trabajo en Bcrypt?

El factor de trabajo determina cuánta capacidad de CPU requiere el proceso de hash. Cuanto más alto sea, más seguro será el hash, pero también tardará más en calcularse. En Node.js, esto también puede afectar directamente el bucle de eventos y la capacidad de respuesta de tu aplicación, según cómo generes el hash.

4. Previene ataques de inyección SQL con Knex.js

La inyección SQL es una vulnerabilidad de seguridad común que representa un riesgo importante para los datos de las aplicaciones. Los desarrolladores de Node.js pueden reducir este riesgo usando Knex.js, un prometedor generador de consultas SQL que permite crear consultas más seguras. Sin embargo, ten en cuenta que Knex.js también permite crear y ejecutar consultas SQL sin procesar. En ese caso, la inyección SQL sigue siendo un riesgo de seguridad, ya que puede introducirse en la consulta al concatenar entradas de usuarios.

Knex.js es un potente generador de consultas SQL para Node.js. Es compatible con transacciones, agrupación de conexiones, migraciones y datos iniciales, por lo que es una opción preferida por los desarrolladores que buscan escribir consultas SQL seguras, sólidas y escalables. Knex.js protege contra los ataques de inyección SQL mediante consultas parametrizadas y el escape de los valores que se ingresan en las instrucciones SQL.

Considera el siguiente fragmento de código, que muestra cómo Knex.js protege contra los ataques de inyección SQL:

const knex = require('knex')({
  client: 'pg',
  connection: {
    host : '127.0.0.1',
    user : 'your_database_user',
    password : 'your_database_password',
    database : 'myapp_test'
  }
});

// This value is provided by the user and could be malicious
// such as applying an OR 1=1 SQL injection or other
// techniques that escape the original context of the query
// and create a new one
let userProvidedValue = 'maliciousValue';

knex('users')
  .where('id', '=', userProvidedValue)
  .select()
  .then(rows => {
    // process result
  })
  .catch(err => {
    // handle error
  });

En este ejemplo, Knex.js aplica automáticamente el escape a userProvidedValue para prevenir posibles ataques de inyección SQL.

Como mencionamos antes, usar Knex.js por sí solo no ofrece un aislamiento completo ni una mitigación total contra la inyección SQL. Este es un ejemplo de un uso potencialmente inseguro de Knex.js que podría provocar un ataque de inyección SQL:

knex.raw(`SELECT * FROM users WHERE id = ${userProvidedValue}`)

En este caso, no se aplica escape ni parametrización a userProvidedValue, por lo que la consulta es vulnerable a la inyección SQL si userProvidedValue contiene código SQL malicioso. Snyk puede ayudar a los desarrolladores a detectar este tipo de vulnerabilidades en su base de código. Snyk analiza tu código y ofrece recomendaciones prácticas para corregir las vulnerabilidades de seguridad, incluidos los posibles ataques de inyección SQL.

Encuentra y corrige vulnerabilidades de JavaScript

Protege tus aplicaciones con el análisis de vulnerabilidades y las recomendaciones para corregirlas de Snyk. 

No se requiere tarjeta de crédito.

O regístrate con Azure AD Docker ID Bitbucket

Al usar Snyk, aceptas cumplir nuestras políticas, incluidos nuestros Términos del servicio y nuestra Política de privacidad.

Preguntas frecuentes sobre la prevención de la inyección SQL con Knex.js

¿Qué es una inyección SQL?

Inyección SQL es una técnica en la que un atacante inserta código SQL malicioso en una consulta. Esta intrusión puede provocar acceso no autorizado a datos confidenciales, corrupción de datos o incluso pérdida de datos. Por eso, prevenir los ataques de inyección SQL es fundamental para la seguridad de las aplicaciones.

¿Knex.js es inmune a las inyecciones SQL?

Aunque Knex.js ayuda mucho a mitigar los riesgos de inyección SQL mediante consultas parametrizadas y el escape de valores, no es completamente inmune. Los desarrolladores deben asegurarse de usar Knex.js correctamente y de no introducir vulnerabilidades.

¿Es suficiente usar Knex.js para prevenir la inyección SQL?

Aunque Knex.js es una herramienta eficaz para prevenir la inyección SQL, no es una solución independiente. Los desarrolladores también deben adoptar otras prácticas recomendadas de seguridad, como la validación de entradas y el principio de privilegio mínimo.

5. Implementa la limitación de frecuencia con fastify-rate-limit

La limitación de frecuencia es un mecanismo de seguridad fundamental que protege tus aplicaciones web contra los ataques de denegación de servicio (DoS). Al controlar la cantidad de solicitudes que un cliente puede hacer a tu aplicación en un periodo determinado, evitas que clientes maliciosos saturen tu servidor con una gran cantidad de solicitudes y garantizas que tu aplicación siga disponible para otros usuarios legítimos.

fastify-rate-limit es un complemento para el framework web Fastify que ofrece una interfaz fácil de usar para implementar la limitación de frecuencia en tus aplicaciones de Node.js. El complemento te permite especificar la cantidad máxima de solicitudes que un cliente puede hacer en un periodo determinado y la respuesta que se debe enviar cuando se supera ese límite.

Este es un ejemplo básico de cómo implementar la limitación de frecuencia en tu aplicación de Node.js con fastify-rate-limit:

const fastify = require('fastify')()

fastify.register(require('@fastify/rate-limit'), {
  // max number of connections during windowMs milliseconds before sending a 429 response
  max: 100,
  timeWindow: '1 minute'
})

fastify.get('/', (req, reply) => {
  reply.send({ hello: 'world' })
})

fastify.listen(3000, err => {
  if (err) throw err
  console.log('Server listening at http://localhost:3000')
})

En este fragmento de código, el paquete de limitación de frecuencia de Fastify está configurado para limitar a cada cliente a 100 solicitudes por minuto. Si un cliente supera este límite, el servidor responde con el código de estado 429 ("Too Many Requests").

Preguntas frecuentes sobre la limitación de frecuencia

¿La limitación de solicitudes puede afectar el rendimiento de mi aplicación?

No, la limitación de solicitudes mejora el rendimiento y la disponibilidad de tu aplicación al evitar que se vea sobrecargada por una gran cantidad de solicitudes.

¿Qué debo hacer si un usuario legítimo supera el límite de solicitudes?

Según las necesidades de tu aplicación, puedes aumentar el límite de solicitudes, excluir ciertas direcciones IP de la limitación de solicitudes o implementar una estrategia más sofisticada que tenga en cuenta el comportamiento y la reputación del usuario.

¿La limitación de velocidad puede prevenir todos los tipos de ataques de denegación de servicio (DoS)?

La limitación de velocidad es eficaz contra los ataques DoS que buscan saturar tu servidor con una gran cantidad de solicitudes. Sin embargo, no puede proteger contra otros tipos de ataques DoS que aprovechan vulnerabilidades específicas de tu aplicación o servidor. Por eso, es importante implementar una estrategia de seguridad integral que incluya la limitación de velocidad y otras medidas de seguridad.

Por qué los desarrolladores deberían usar Snyk para la seguridad de JavaScript

En esta publicación del blog, repasamos cinco fragmentos esenciales de código de seguridad de Node.js que todo desarrollador backend debería conocer. Hablamos de la importancia de las prácticas de programación segura, como evitar la inyección SQL, y de su importante contribución a la seguridad de las aplicaciones. Con la evolución de la tecnología y la creciente complejidad de las amenazas cibernéticas, no se puede exagerar la importancia de programar de forma segura.

Como desarrollador, es fundamental aprovechar herramientas de seguridad sólidas que te ayuden a identificar y mitigar posibles vulnerabilidades en tu código. Una de ellas es Snyk, una potente plataforma de seguridad para desarrolladores que permite detectar y corregir vulnerabilidades en el código, las dependencias, los contenedores y mucho más. Con Snyk, puedes monitorear continuamente tu aplicación para detectar vulnerabilidades de seguridad y recibir solicitudes de cambios de corrección automáticas cuando se descubre una nueva vulnerabilidad.

// Install Snyk CLI
npm install -g snyk

// Then run Snyk to find vulnerabilities
snyk test

Snyk se integra sin problemas en tus IDE y tu canalización de CI/CD, lo que te permite mantener la seguridad de forma continua durante todo el ciclo de vida del desarrollo de tus aplicaciones. Es compatible con una amplia variedad de lenguajes de programación, incluido Node.js, por lo que es una herramienta versátil para desarrolladores que trabajan en distintas plataformas.

La programación segura es una práctica fundamental en el desarrollo de software que todos los desarrolladores de backend deberían priorizar. El uso de herramientas de seguridad como Snyk refuerza esta práctica al ofrecer capacidades automatizadas para detectar vulnerabilidades y corregirlas. A medida que seguimos enfrentando un panorama de ciberamenazas en constante evolución, estas prácticas y herramientas serán esenciales para mantener la seguridad y la integridad de nuestras aplicaciones.

Snyk para la seguridad de JavaScript

Desde la primera línea de código hasta la última dependencia de npm, Snyk protege tus aplicaciones de JavaScript directamente desde tu IDE, CLI y flujos de trabajo de Git.

Leer más

Blog

Los modelos de frontera encontraron las vulnerabilidades. Solo el atacante encontró las cadenas.

El análisis estático encontró las fallas, pero solo las pruebas de ataque en vivo demostraron cómo podían encadenarse para provocar brechas. Una comparación de Evo COS, Claude Security y Claude Code Security.

feature insights context
Blog

Los ataques autónomos ya están aquí. La defensa debe estar a su altura.

Los atacantes autónomos están reduciendo el tiempo disponible para defenderse. Descubre cómo el descubrimiento, la corrección, la validación y la prevención continuos pueden ayudar a los equipos de seguridad a seguirles el ritmo.

Blog

Por qué los agentes de programación con IA siguen generando fallas de control de acceso

Los agentes de programación con IA pueden generar lógica de autorización que compila y supera la revisión, pero expone los datos de un inquilino a otro. Descubre por qué es difícil detectar el control de acceso roto y cómo prevenirlo.