Saltar al contenido
Lección 6 de 8

Configuraciones de Seguridad Inseguras

9 min read

Qué es la Configuración de Seguridad Inadecuada

La configuración de seguridad inadecuada (A05:2021 en el OWASP Top 10) abarca todos los fallos que surgen no del código de la aplicación, sino de cómo está configurada la plataforma que la rodea: el servidor web, el framework, la base de datos, el contenedor, el proveedor de nube. Es una de las categorías más comunes porque hay muchísimas piezas que configurar y cualquier valor por defecto inseguro o ajuste olvidado abre una puerta.

A diferencia de la inyección o el XSS, que dependen de un fallo concreto en una línea de código, la mala configuración es transversal y a menudo invisible hasta que alguien la encuentra. Ejemplos típicos: cuentas por defecto con contraseñas conocidas, funciones de depuración activas en producción, permisos de archivos demasiado abiertos, servicios innecesarios expuestos y mensajes de error que revelan detalles internos del sistema.

El Mecanismo y su Impacto Real

El impacto de una mala configuración rara vez requiere un exploit sofisticado: basta con que un recurso quede accesible para quien no debería verlo. Estos son los patrones que aparecen una y otra vez en las brechas reales:

  • Almacenamiento en la nube público por accidente. Un bucket o contenedor de objetos (por ejemplo un bucket de almacenamiento con la política de acceso configurada como pública) que debía ser privado queda expuesto a Internet. Cualquiera que descubra la URL descarga respaldos, documentos internos o datos personales sin autenticarse. La causa casi siempre es un ajuste por defecto permisivo o una política copiada sin revisar.
  • Paneles de administración expuestos. Un /admin, un panel de métricas o una consola de base de datos accesibles desde Internet, a veces todavía con las credenciales de fábrica, entregan el control del sistema a quien los encuentre.
  • Artefactos del repositorio accesibles. Un directorio .git servido públicamente permite reconstruir el código fuente y, con él, secretos incrustados. Lo mismo ocurre con archivos de respaldo (backup.zip, db.sql) olvidados en la raíz web.
  • Depuración activa en producción. El modo debug del framework encendido en producción convierte cada error en una fuente de información: rutas del sistema, variables de entorno, fragmentos de consultas y a veces una consola interactiva.

El denominador común es el mismo: un valor por defecto pensado para comodidad en desarrollo se llevó tal cual a producción. La defensa no es un parche, sino disciplina de configuración.

Cabeceras de Seguridad HTTP

Los navegadores modernos ofrecen muchos mecanismos de protección que solo se activan si el servidor envía las cabeceras de seguridad adecuadas. Omitirlas es una forma frecuente de configuración insegura. La cabecera Content-Security-Policy (CSP), que vimos en la lección de XSS, controla desde dónde puede cargar recursos la página y es una de las defensas más potentes contra la inyección de scripts.

La cabecera Strict-Transport-Security (HSTS) obliga al navegador a usar siempre HTTPS, evitando ataques de degradación a HTTP. X-Content-Type-Options: nosniff impide que el navegador interprete archivos con un tipo distinto al declarado. X-Frame-Options o la directiva frame-ancestors de CSP protegen contra el clickjacking impidiendo que tu sitio se embeba en un iframe malicioso. Y Referrer-Policy controla cuánta información de la URL se filtra al navegar a otros sitios. Configurar estas cabeceras es de bajo costo y alto impacto.

Vulnerable: sin cabeceras, con firma expuesta y errores detallados

Una app Express recién creada revela su tecnología en X-Powered-By, no envía ninguna cabecera de protección y filtra el stack trace al cliente cuando algo falla:

import express from "express";

const app = express();

// x-powered-by: Express se envía por defecto y delata el framework.
app.get("/", (req, res) => res.send("Hola"));

// Handler de error que filtra detalles internos al cliente.
app.use((err, req, res, next) => {
  res.status(500).send(`<pre>${err.stack}</pre>`); // rutas, versiones, código
});

app.listen(3000);

Seguro: helmet, firma desactivada y errores discretos

Usamos helmet para establecer las cabeceras, desactivamos x-powered-by y devolvemos un mensaje genérico en producción mientras registramos el detalle solo en el servidor:

import express from "express";
import helmet from "helmet";

const app = express();
const isProd = process.env.NODE_ENV === "production";

app.disable("x-powered-by");

app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'"],
        frameAncestors: ["'none'"], // anti-clickjacking
      },
    },
    hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
    referrerPolicy: { policy: "strict-origin-when-cross-origin" },
  })
);
// helmet ya aplica X-Content-Type-Options: nosniff y X-Frame-Options: DENY.

app.get("/", (req, res) => res.send("Hola"));

app.use((err, req, res, next) => {
  console.error(err); // el detalle queda solo en los logs del servidor
  res.status(500).json({ error: "Error interno del servidor" });
});

app.listen(3000);

El resultado en la red es un conjunto de cabeceras como este, que conviene verificar con las herramientas del navegador:

Content-Security-Policy: default-src 'self'; script-src 'self'; frame-ancestors 'none'
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin

Exposición de Datos Sensibles

Una mala configuración suele traducirse en exposición de datos sensibles. Los casos más comunes incluyen listados de directorios habilitados que muestran la estructura de archivos del servidor, archivos de respaldo o .git accesibles públicamente, mensajes de error detallados que revelan rutas, versiones de software o fragmentos de consultas, y endpoints de administración o métricas expuestos sin autenticación.

Otro patrón crítico es transmitir o almacenar datos sensibles sin la protección adecuada: tráfico sin HTTPS, contraseñas con hashing débil (relacionado con A02:2021 — Cryptographic Failures), o información personal devuelta en respuestas de API que no la necesitan. La regla práctica es minimizar: no recolectes, no almacenes y no devuelvas datos sensibles que no sean estrictamente necesarios, y cuando lo hagas, protégelos en tránsito y en reposo. Una respuesta de API que serializa el objeto de usuario completo (con su hash de contraseña o su token de sesión) es un fallo de minimización tan real como un bucket público: expón solo los campos que la vista necesita.

Manejo Seguro de Secretos

Los secretos (claves de API, contraseñas de base de datos, tokens de servicios, claves de cifrado) son uno de los activos más peligrosos de gestionar mal. Un error clásico y muy frecuente es incrustar secretos en el código fuente y subirlos a un repositorio. Una vez que un secreto entra en el historial de Git, eliminarlo del último commit no basta: queda en el historial y debe considerarse comprometido y rotado.

Vulnerable: clave de API incrustada en el código

// NUNCA hagas esto: el secreto queda en el repositorio y en su historial.
const apiKey = "sk_live_9f3a2b7c8d1e4f5a6b7c8d9e0f1a2b3c";
const client = new PaymentClient(apiKey);

Seguro: secreto en variable de entorno con validación al arrancar

Leemos el secreto de process.env, fallamos rápido si falta y nos aseguramos de que el archivo con los valores no llegue al repositorio:

// config.js
const apiKey = process.env.API_KEY;

if (!apiKey) {
  // Fail-fast: la app no arranca sin su configuración obligatoria.
  throw new Error("Falta la variable de entorno API_KEY");
}

export const client = new PaymentClient(apiKey);
# .env  (con el valor real, NUNCA versionado)
API_KEY=TU_CLAVE
# .gitignore
.env
.env.*
*.pem

La práctica correcta es mantener los secretos fuera del código: en variables de entorno, en archivos de configuración no versionados, o idealmente en un gestor de secretos dedicado (como Vault o los servicios de secretos de los proveedores de nube). Escanea tus repositorios con herramientas de detección de secretos, establece la rotación periódica de credenciales y trata cada secreto como si su exposición fuera inevitable algún día: minimiza su alcance y prepara su rotación.

Endurecimiento y Verificación

El hardening (endurecimiento) es el proceso de reducir la superficie de ataque de un sistema configurándolo de forma segura. Empieza por desactivar todo lo que no necesitas: módulos, servicios, puertos, cuentas y funciones de ejemplo. Cambia todas las credenciales por defecto. Desactiva el modo debug y los errores detallados en producción. Fuerza HTTPS/TLS en todo el tráfico. Mantén el software actualizado y aplica los parches de seguridad con prontitud, ya que muchas configuraciones inseguras provienen de versiones obsoletas.

Adopta una configuración repetible y auditable: define entornos idénticos mediante infraestructura como código (IaC), de modo que la configuración segura se aplique de forma consistente en desarrollo, pruebas y producción, sin desviaciones manuales. Una configuración escrita en código se revisa, se versiona y se despliega igual en todas partes, eliminando el "funciona en mi máquina" que tantas brechas provoca.

Verifica tu postura con herramientas concretas:

  • OWASP ZAP y escáneres de configuración para detectar cabeceras faltantes, cookies inseguras y endpoints expuestos.
  • Herramientas de detección de secretos como gitleaks o trufflehog, integradas en el flujo de CI para bloquear commits que contengan credenciales.
  • CIS Benchmarks como guía de referencia para endurecer sistemas operativos, servidores web, bases de datos y contenedores.
  • Dependabot (o equivalente) para mantener las dependencias y las imágenes base actualizadas frente a vulnerabilidades conocidas.

Como la configuración cambia con el tiempo, conviértela en parte de tu monitoreo continuo en lugar de una tarea que se hace una vez y se olvida.

Checklist de Prevención

  • Elimina o desactiva servicios, puertos, módulos, cuentas y páginas de ejemplo que no uses.
  • Cambia todas las credenciales por defecto antes de exponer cualquier servicio.
  • Envía las cabeceras de seguridad (CSP, HSTS, nosniff, X-Frame-Options/frame-ancestors, Referrer-Policy); en Node usa helmet y app.disable('x-powered-by').
  • Desactiva el modo debug y los mensajes de error detallados en producción; registra el detalle solo en el servidor.
  • Fuerza HTTPS/TLS en todo el tráfico y activa HSTS.
  • Verifica que buckets, contenedores de objetos y paneles de administración no sean públicos.
  • Bloquea el acceso a .git, archivos de respaldo y artefactos de build desde la web.
  • Minimiza los datos: no recolectes, no almacenes ni devuelvas información sensible que no sea necesaria.
  • Mantén los secretos fuera del código: variables de entorno, .gitignore y un gestor de secretos (Vault, secret managers de nube); valida su presencia al arrancar y rótalos periódicamente.
  • Escanea el repositorio con gitleaks/trufflehog y las apps con OWASP ZAP de forma recurrente.
  • Define la configuración con IaC para entornos repetibles y audítala contra los CIS Benchmarks.
  • Mantén dependencias e imágenes base actualizadas con Dependabot o equivalente.