Errores de seguridad comunes en JavaScript y cómo evitarlos

Publicado el 02 noviembre 2024 por José María Acuña Morgado @jmacuna73
JavaScript es una de las tecnologías centrales del desarrollo web moderno y debido a su popularidad, también es uno de los principales objetivos de ataques en aplicaciones web. Existen numerosas vulnerabilidades que los desarrolladores deben conocer para proteger sus aplicaciones y la información de sus usuarios.
La seguridad en aplicaciones web se ha convertido en una prioridad fundamental para desarrolladores y empresas debido al incremento de ciberataques que comprometen la información personal y los datos sensibles de los usuarios. Según un informe del proyecto OWASP (Open Web Application Security Project), el 75% de las aplicaciones web actuales son vulnerables a algún tipo de ataque, siendo Cross-Site Scripting (XSS) uno de los errores más comunes y peligrosos (OWASP Top 10).
Un estudio de Verizon reveló que el 40% de las violaciones de datos en el 2020 fueron atribuidas a ataques de inyección, incluyendo XSS y otras formas de inyección de código (Verizon Data Breach Investigations Report).
Por otro lado, el informe de Cybersecurity Ventures proyecta que los daños económicos causados por el cibercrimen podrían alcanzar los 10.5 trillones de dólares anuales para el año 2025, lo que refleja no solo el impacto en las empresas, sino también la pérdida de confianza de los usuarios en plataformas digitales (Cybersecurity Ventures 2022 Report).
Image on Freepik
A continuación, analizaremos algunos de los errores de seguridad más comunes en JavaScript y cómo prevenirlos. Incluiremos ejemplos prácticos y detalles técnicos para ayudarte a escribir código más seguro y minimizar los riesgos.

1. Cross-Site Scripting (XSS)


Uno de los errores de seguridad más comunes en JavaScript es Cross-Site Scripting (XSS), que ocurre cuando un atacante inserta código malicioso en una aplicación web, permitiendo que el código se ejecute en el navegador de otros usuarios. Este tipo de vulnerabilidad se suele explotar mediante formularios, campos de entrada de texto o incluso parámetros en la URL.
// Vulnerable a XSS si el contenido de `userComment` 
// no se sanitiza adecuadamente
document.getElementById("output").innerHTML = userComment;

La manera correcta de evitar XSS es usar una función de sanitización de datos que escape cualquier entrada potencialmente peligrosa. Alternativamente, si se está utilizando una librería de frontend como React, es mejor usar sus métodos internos para manejar el DOM, ya que aplican sanitización de manera automática.
// Escapando contenido para prevenir XSS
document.getElementById("output").textContent = userComment;

Además, se recomienda usar Content Security Policy (CSP) para restringir los orígenes de contenido y reducir las posibilidades de inyecciones de script.

2. Inyección de código (Code Injection)


La inyección de código es otro problema serio cuando un atacante logra insertar y ejecutar código malicioso en una aplicación a través de datos de entrada no controlados, permitiéndoles controlar el flujo de ejecución, acceder a funciones no autorizadas o comprometer la integridad de la aplicación.
// Uso inseguro de eval puede ejecutar código malicioso
let userCode = "alert('hacked')";
eval(userCode);  // Vulnerable a inyección de código

Evitar el uso de funciones como eval(), setTimeout() o setInterval() con cadenas de texto generadas por el usuario. En lugar de eval(), utiliza alternativas seguras que no evalúen el código sin control.
// En lugar de usar eval
let safeFunction = new Function('return Math.random() * 100;');
safeFunction();

3. Manipulación de datos con JSON


JavaScript permite el uso extensivo de JSON (JavaScript Object Notation) para el intercambio de datos. Sin embargo, el uso incorrecto de JSON.parse con datos no confiables puede llevar a vulnerabilidades de inyección.
// Si data proviene de una fuente insegura, 
// puede ser problemático
let userData = JSON.parse(data);

Asegúrate de validar y sanear los datos recibidos antes de parsearlos. Si es posible, usa una biblioteca de validación como Joi para asegurarte de que solo se acepten los datos en el formato esperado.
// Validación de estructura JSON antes de parsear
try {
    const parsedData = JSON.parse(data);
    if (parsedData.hasOwnProperty('expectedProperty')) {
        // Procesa los datos con seguridad
    }
} catch (error) {
    console.error("JSON inválido recibido");
}

4. Inyección de comandos (Command Injection)


Si una aplicación usa JavaScript en el backend con Node.js y permite ejecutar comandos en el sistema operativo, puede ser vulnerable a inyecciones de comandos, especialmente si usa datos de entrada de los usuarios para construir comandos.
const { exec } = require("child_process");
exec("ls " + userInput, (error, stdout, stderr) => {
    if (error) {
        console.error(`Error: ${error.message}`);
    }
});

Usa las funciones seguras de Node.js como execFile, que aceptan cada argumento por separado y no permiten que se interprete como un comando completo.
const { execFile } = require("child_process");
execFile("ls", [userInput], (error, stdout, stderr) => {
    if (error) {
        console.error(`Error: ${error.message}`);
    }
});

5. Fuga de información a través de consola


Un error común en JavaScript es dejar mensajes de depuración en la consola, ofreciendo a los atacantes información sobre la estructura interna de la aplicación, especialmente en el entorno de producción.
console.log("Usuario autenticado: ", userCredentials);

Es imperativo deshabilitar los logs y mensajes de consola en entornos de producción, especialmente aquellos que revelen información confidencial.
if (process.env.NODE_ENV !== 'production') {
    console.log("Mensaje de depuración");
}

6. Validación de entrada inadecuada


La validación de entrada insuficiente es una causa común de vulnerabilidades de seguridad. En JavaScript, se tienen que validar las entradas en el cliente y en el servidor, asegurando que solo se introduzcan en el sistema datos en el formato correcto.
// Validación de entrada insuficiente
if (userInput.length > 10) {
    processData(userInput);
}

Implementa una validación de entrada robusta y utiliza bibliotecas que aseguren la validación estricta de tipos y formato. Librerías como validator.js pueden ayudar a validar emails, URLs y otros tipos de datos comunes.
const validator = require('validator');
if (validator.isEmail(userInput)) {
    processData(userInput);
}

Los errores comunes de seguridad, como Cross-Site Scripting, la inyección de código y la fuga de información, pueden afectar tanto la integridad como la privacidad de los usuarios. Para mitigarlos, es fundamental seguir buenas prácticas de programación y mantener actualizadas las dependencias, así como realizar auditorías de seguridad periódicas. Al invertir en la seguridad de tus aplicaciones, puedes evitar problemas graves en el futuro y ofrecer a tus usuarios una experiencia más confiable y segura.
Jose Maria Acuña Morgado - Web Developer - Ethical Hacking