Skip to main content

Cómo prevenir ataques de inyección SQL en Node.js

Escrito por
Headshot of Lucien Chemaly

Lucien Chemaly

20 de febrero de 2024

0 minutos de lectura

A medida que aumenta nuestra dependencia de los sistemas de software, también surgen numerosas amenazas de seguridad. Una amenaza importante para los desarrolladores, especialmente quienes trabajan con Node.js, es la inyección SQL.

La inyección SQL es un ataque malicioso en el que se introduce código SQL dañino en un sistema para exponer información confidencial, corromper o eliminar datos y, en ocasiones, permitir que los atacantes obtengan acceso no autorizado. Hacer frente a esta amenaza es difícil y pone de relieve la necesidad de proteger tus aplicaciones de Node.js.

En este artículo, aprenderás por qué los ataques de inyección SQL representan una amenaza importante y cómo proteger tus aplicaciones de Node.js.

Cómo entender la inyección SQL en aplicaciones de Node.js

Los ataques de inyección SQL suelen ocurrir cuando una aplicación de Node.js no maneja correctamente los datos que ingresan los usuarios. Si se usan estos datos en una consulta SQL sin validarlos, depurarlos o construir la consulta correctamente, se crea una brecha que permite a los atacantes manipularla y obtener acceso no autorizado o filtrar datos. Si quieres consultar las vulnerabilidades pasadas y recientes de Node.js, encontrarás una lista en Snyk Vulnerability Database.

Aplicación de Express Node.js con una vulnerabilidad de inyección SQL

Para entender mejor cómo funciona la inyección SQL, creemos rápidamente una aplicación vulnerable con Node.js, Express y una base de datos PostgreSQL. La aplicación recibe los datos del usuario desde un formulario, construye una consulta SQL y la ejecuta en la base de datos para obtener información.

Primero, descarga e instala Node.js en tu equipo. Node.js es el entorno de ejecución que ejecuta tu código JavaScript en el servidor.

Luego, crea un directorio para tu proyecto (es decir, vulnerable-app) y, en la terminal, ve a ese directorio con el comando cd vulnerable-app.

Dentro del directorio del proyecto, ejecuta el comando npm init -y. Este comando crea un archivo package.json con valores predeterminados, que funciona como archivo de manifiesto de tu proyecto de Node.js.

Instala Express y la biblioteca pg con el comando npm install express pg. Express es un framework ligero para aplicaciones web, y pg es un cliente de Node.js para PostgreSQL.

A continuación, descarga e instala PostgreSQL. Una vez instalado, crea una base de datos llamada vulnerable_db. Dentro de esta base de datos, crea una tabla llamada users con una columna llamada name. El comando SQL es el siguiente:

   CREATE TABLE users (
       id SERIAL PRIMARY KEY,
       name VARCHAR(255)
   );

Para obtener información más detallada sobre cómo instalar y ejecutar PostgreSQL, consulta esta documentación de W3Schools.

Crea un archivo llamado app.js y agrega el siguiente código:

const express = require("express");
const { Client } = require("pg");
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json()); // To handle JSON input
const client = new Client({
  host: "your-host",
  database: "your-database",
  username: "your-username",
  password: "your-password",
  port: "your-port",
});
client.connect();
app.post("/search", (req, res) => {
  const userInput = req.body.userInput;
  const query = "SELECT * FROM users WHERE name = '" + userInput + "';";
  client.query(query, (err, result) => {
    if (err) throw err;
    res.send(result.rows);
  });
});
app.post("/create", (req, res) => {
  const { name } = req.body;
  if (!name) {
    res.status(400).send("Name is required");
    return;
  }
  const query = "INSERT INTO users (name) VALUES ($1) RETURNING *;";
  client.query(query, [name], (err, result) => {
    if (err) {
      console.error(err);
      res.status(500).send("Internal Server Error");
      return;
    }
    res.send(result.rows[0]); // Respond with the created user record
  });
});
app.listen(3000, () => {
  console.log("Server is running on port 3000");
});

Este código inicializa un servidor de Express y configura conexiones a una base de datos PostgreSQL mediante la biblioteca pg. Configura el servidor para manejar datos de entrada codificados en URL y en formato JSON.

Se definen dos rutas: una ruta POST /search y una ruta POST /create. La ruta /search toma los datos del usuario del cuerpo de la solicitud, construye una consulta SQL, la ejecuta en la base de datos y responde con el resultado. Sin embargo, presenta una vulnerabilidad de inyección SQL porque concatena los datos del usuario en la consulta. La ruta /create extrae un valor name del cuerpo de la solicitud, comprueba que esté presente y luego construye una consulta SQL parametrizada segura para insertar un nuevo registro de usuario en la base de datos; responde con el registro creado. Por último, el servidor comienza a escuchar en el puerto 3000 e indica que está listo mediante un mensaje en la consola.

Nota: Asegúrate de reemplazar your-username, your-password, your-database, your-host y your-port por la configuración que definiste para PostgreSQL.

Como no hay validación ni depuración de los datos de entrada, el código es vulnerable a la inyección SQL. Un atacante puede ingresar un valor como ' OR '1'='1'; -- en el formulario. Este valor termina la consulta original y la modifica a SELECT * FROM users WHERE name = '' OR '1'='1'; --';, que devuelve todos los registros de la tabla users, ya que '1'='1' siempre es verdadero. Este es un ejemplo clásico de inyección SQL mediante concatenación de cadenas.

Ahora que todo está configurado, es momento de iniciar el servidor. Ejecuta el comando node app.js en la terminal. Esto iniciará el servidor y deberías ver el mensaje Server is running on port 3000, que indica que se está ejecutando correctamente:

Terminal que muestra una aplicación de Node.js iniciada con node app.js y el mensaje «Server is running on port 3000».

Para probar la aplicación, debes crear algunos usuarios desde la terminal o el shell:

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User1"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User2"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User3"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User4"}'

curl -X POST http://localhost:3000/create -H "Content-Type: application/json" -d '{"name":"User5"}'

Luego, si quieres buscar a un usuario específico para verificarlo, actualizar sus datos o realizar auditorías y monitoreo, debes enviar una solicitud de búsqueda de sus datos. Ejecuta el siguiente comando para continuar:

curl -X POST http://localhost:3000/search -H "Content-Type: application/json" -d '{"userInput":"User1"}'

El resultado debería verse así:

[{"id":1,"name":"User1"}]

Ahora, agrega una inyección SQL para obtener todos los usuarios con el siguiente comando:

curl -X POST http://localhost:3000/search -H "Content-Type: application/json" -d "{\"userInput\":\"' OR '1'='1'; -- \"}"

El resultado debería verse así:

[{"id":1,"name":"User1"},{"id":2,"name":"User2"},{"id":3,"name":"User3"},{"id":4,"name":"User4"},{"id":5,"name":"User5"},{"id":6,"name":"User2"},{"id":7,"name":"User2"},{"id":8,"name":"User3"},{"id":9,"name":"User4"},{"id":10,"name":"User5"}]

En este comando, los datos JSON están delimitados por comillas dobles externas (" ") para formar una cadena JSON completa, mientras que las comillas internas de los datos JSON se escapan con una barra invertida (\) para incluirlas correctamente en la cadena JSON. El valor de userInput es ' OR '1'='1'; -- y, como se explicó antes, manipula la consulta SQL para devolver todos los registros de usuarios al modificar la condición de la consulta para que siempre sea verdadera.

Cómo prevenir vulnerabilidades de inyección SQL en Node.js

Ahora que sabes lo fácil que es que ocurran ataques de inyección SQL, es momento de conocer algunas formas de proteger tu aplicación y mantenerla sólida y segura para tus usuarios.

Usa marcadores de posición en las consultas

Evitar la construcción de consultas SQL mediante la concatenación de cadenas es fundamental para prevenir la inyección SQL. Los atacantes pueden manipular las consultas al insertar código SQL malicioso en los campos de entrada. En su lugar, usa marcadores de posición en las consultas para garantizar que los datos del usuario se traten como datos y no como código ejecutable:

app.post('/search', (req, res) => {
    const userInput = req.body.userInput;
    const query = "SELECT * FROM users WHERE name = $1;";
    client.query(query, [userInput], (err, result) => {
        if (err) throw err;
        res.send(result.rows);
    });
});

Usa sentencias preparadas

Una de las formas más eficaces de prevenir los ataques de inyección SQL es usar sentencias preparadas. Estas garantizan que los datos del usuario siempre se traten como datos y no como código ejecutable.

Así puedes modificar la ruta /search para usar sentencias preparadas con la biblioteca pg:

app.post('/search', (req, res) => {
    const userInput = req.body.userInput;
    const query = "SELECT * FROM users WHERE name = $1;";
    const preparedQuery = {
        text: query,
        values: [userInput]
    };
    client.query(preparedQuery, (err, result) => {
        if (err) throw err;
        res.send(result.rows);
    });
});

En este código, la sentencia preparada se define como un objeto con una propiedad text para la consulta SQL y una propiedad values para los datos del usuario. Esta separación permite especificar una sola vez la plantilla de la consulta SQL y proporcionar valores distintos para el marcador de posición cada vez que ejecutes la sentencia preparada. Así, es más seguro y eficiente manejar los datos del usuario en las consultas SQL.

Valida y depura los datos del usuario

Verificar los datos del usuario es fundamental para garantizar que cumplan requisitos específicos, como el formato, la longitud o el rango de valores correctos. El uso de bibliotecas de validación como joi o express-validator puede simplificar este proceso. Estas bibliotecas ofrecen reglas y funciones de validación predefinidas que facilitan el cumplimiento de los requisitos de entrada y la automatización del proceso de validación.

Además, depurar los datos de entrada para eliminar o escapar caracteres potencialmente dañinos es fundamental para mantener un entorno de aplicación seguro y sólido. La depuración de datos de entrada consiste en limpiar los datos del usuario para garantizar que no provoquen daños ni comportamientos inesperados cuando la aplicación los procese.

Así puedes implementar la validación y depuración con la biblioteca express-validator:

 const { body, validationResult } = require('express-validator');

app.post("/search", [
  body('userInput').trim().escape().isAlphanumeric(),
], (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }

  const userInput = req.body.userInput;
  const query = "SELECT * FROM users WHERE name = $1;";

  client.query(query, [userInput], (err, result) => {
      if (err) throw err;
      res.send(result.rows);
  });
});

Usa las versiones más recientes de las bibliotecas de ORM y SQL

Asegúrate siempre de usar las versiones más recientes de las bibliotecas de ORM y SQL. Quienes mantienen estas bibliotecas publican actualizaciones y parches con frecuencia para corregir vulnerabilidades de seguridad conocidas:

npm install pg@latest

Revisa el código con regularidad y realiza auditorías de seguridad

Realiza revisiones de código y auditorías de seguridad con regularidad para detectar vulnerabilidades a tiempo. Usa herramientas como npm audit o snyk para automatizar la identificación de vulnerabilidades conocidas en tus dependencias.

Usa la extensión de Snyk para Visual Studio Code para detectar vulnerabilidades

Snyk es una herramienta de seguridad diseñada para desarrolladores que ayuda a identificar y corregir vulnerabilidades en el código y las dependencias de código abierto. Se integra sin problemas en el flujo de trabajo de desarrollo y ofrece monitoreo y análisis continuos para detectar fallas de seguridad, incluidas las relacionadas con la inyección SQL.

Snyk no solo identifica vulnerabilidades, sino que también ofrece pull request de corrección automatizados para facilitar la corrección y prevención de posibles problemas de seguridad. Se integra con diversas plataformas de desarrollo, como GitHub, GitLab y Bitbucket, y ofrece una extensión específica para Visual Studio Code que proporciona comentarios en tiempo real durante el proceso de codificación.

Configura una extensión de Snyk

Configurar una extensión de Snyk es fácil. Para empezar, descarga e instala Visual Studio Code si aún no lo tienes. Una vez instalado, abre Visual Studio Code y accede a la vista Extensions con el atajo Ctrl + Shift + X en Windows o Command + Shift + X en macOS.

En la vista Extensions, escribe "Snyk" en la barra de búsqueda y haz clic en el botón Install de la extensión de Snyk:

Marketplace de Visual Studio Code con una búsqueda de “snyk” y la extensión Snyk Security con botones de instalación.

Después de instalarla, deberías ver la extensión Snyk en la barra de navegación izquierda de Visual Studio Code. Haz clic en ella y luego en Trust workspace and connect. Para usar la extensión, tendrás que iniciar sesión en tu cuenta de Snyk. Si aún no tienes una, puedes crear una gratis:

Visual Studio Code muestra la extensión de Snyk con el botón «Confiar en el espacio de trabajo y conectarse» resaltado y detalles del análisis de vulnerabilidades.

A continuación, se te pedirá que inicies sesión y te autentiques. Una vez hecho esto, deberías ver el mensaje Authenticated:

Página de confirmación de autenticación de Snyk que muestra el mensaje «Autenticado» y «Tu cuenta se autenticó correctamente. Snyk ya está listo para usarse».

Una vez instalada y configurada la extensión de Snyk, esta analiza automáticamente tu proyecto para detectar vulnerabilidades conocidas, incluidos posibles riesgos de inyección SQL.

Ve a tu cuenta en la plataforma de Snyk y sigue las instrucciones para activar Snyk Code. Es importante activar Snyk Code para que Snyk pueda detectar vulnerabilidades en el código, como las inyecciones SQL.

Si la opción Snyk Code no está activada en tu cuenta de Snyk, la extensión de Snyk para Visual Studio Code no detectará las inyecciones SQL relacionadas con malas prácticas de codificación. Solo detectará vulnerabilidades relacionadas con bibliotecas desactualizadas, entre otras.

Snyk no se limita a identificar vulnerabilidades: también ofrece sugerencias y referencias para corregirlas. Esta función puede ser muy útil, especialmente al trabajar con bases de código grandes o plazos ajustados.

Todo el código de este tutorial está disponible en el siguiente repositorio de GitHub.

Conclusión

En este artículo, aprendiste cómo se manifiestan las inyecciones SQL en las aplicaciones de Node.js y conociste varias estrategias para prevenirlas. Desde actualizar tus bibliotecas de ORM y SQL, depurar los datos de entrada y usar marcadores de posición en las consultas, hasta aprovechar la extensión de IDE Snyk para Visual Studio Code, tienes muchas opciones para proteger tus aplicaciones de Node.js contra los ataques de inyección SQL.

La extensión de Snyk no solo ayuda a identificar vulnerabilidades de inyección SQL, sino que también te enseña a resolverlas y actúa como mentor virtual de seguridad. Al integrar Snyk en tu flujo de trabajo de desarrollo, no solo reaccionas ante las amenazas de seguridad, sino que las previenes de forma proactiva y te aseguras de que tus aplicaciones sean sólidas y seguras antes de llegar a producción.

A medida que sigues avanzando en el desarrollo con Node.js, recuerda que la seguridad no es una tarea que se realiza una sola vez, sino un proceso continuo. Mantente al día con las mejores prácticas de seguridad, sigue aprendiendo y protege tus aplicaciones. Tus usuarios te lo agradecerán, y tendrás la tranquilidad de saber que tus datos y los de tus usuarios están seguros.

Empieza con los desafíos de Capture the Flag

Aprende a resolver desafíos de captura la bandera con nuestro taller virtual introductorio a pedido.

Leer más

blog feature ai lilac
Blog

Convierte tu backlog de vulnerabilidades en pull requests con la remediación agéntica

Evo Remediation Agent ya está disponible en Open Preview e incorpora la resolución automatizada de dependencias a Evo Agentic AppSec. Ahora, los equipos de AppSec pueden ejecutar campañas de remediación continuas

AI Background min crop
Blog

La IA activa todos los riesgos que has aceptado: siete ideas del panel de seguridad de IA sin filtros

La IA hace que los ataques sean más baratos y rápidos, y deja al descubierto riesgos que las organizaciones ya enfrentan. Cuatro líderes de seguridad explican por qué la defensa a velocidad de máquina y la corrección más rápida son esenciales.

blog feature ai violet
Blog

VulnBump: ¿Pueden los agentes de IA corregir dependencias vulnerables sin romper la compilación?

VulnBump prueba agentes de IA con 307 actualizaciones reales de dependencias. Una corrección cuenta solo si elimina la vulnerabilidad y la compilación y las pruebas siguen pasando.