Puedes tener un diseño sólido (A04) y un código sin inyecciones (A03), y aun así quedar expuesto por cómo está configurado y desplegado el sistema. A05:2021 – Configuración de Seguridad Incorrecta (Security Misconfiguration) recoge los fallos que no están en tu lógica, sino en los ajustes: valores por defecto inseguros, cuentas y contraseñas de fábrica, mensajes de error que revelan de más, cabeceras de seguridad ausentes, servicios y puertos innecesarios, permisos laxos. Es una de las categorías más frecuentes, precisamente porque el software moderno tiene cientos de opciones y muchas vienen "abiertas por comodidad".

En el Top Ten 2021, esta categoría absorbió el antiguo XXE (XML External Entities), que no es más que un parser XML mal configurado. Aquí lo mencionamos como parte de A05, pero por su técnica específica lo desarrollamos a fondo en la lección 03-07. En BazarNube revisaremos la configuración de la API Express y de los contenedores Docker, dos focos habituales de mala configuración.

Aviso legal y ético: los ejemplos son ilustrativos y con datos ficticios. Practica solo sobre sistemas propios o con autorización explícita.

Contenido

  1. Qué abarca la configuración incorrecta
  2. Cabeceras de seguridad ausentes en Express
  3. Mensajes de error verbosos y modo de desarrollo en producción
  4. Cuentas y valores por defecto; superficie innecesaria
  5. Hardening de contenedores Docker
  6. XXE como configuración de parser (remitido a 03-07)
  7. Proceso: hardening reproducible y automatizado
  8. Errores comunes, ejercicios y soluciones

  1. Qué abarca la configuración incorrecta

Un vistazo a la variedad de fallos que caen bajo A05:

Área Fallo típico Riesgo
Cabeceras HTTP Faltan HSTS, X-Content-Type-Options, CSP XSS, sniffing, clickjacking
Errores Stack traces al cliente Fuga de información interna
Cuentas Usuario/clave por defecto sin cambiar Acceso trivial
Servicios Puertos/paneles de administración expuestos Superficie de ataque
Framework Modo debug activo en producción Fuga de datos, ejecución
Parsers XML con entidades externas habilitadas XXE (03-07)
Permisos Ficheros/buckets con acceso público Exposición de datos

El hilo común: valores por defecto pensados para "que funcione", no para "que sea seguro", que nunca se endurecieron.

  1. Cabeceras de seguridad ausentes en Express

La API de BazarNube arrancó sin cabeceras de seguridad y, peor, revelando su tecnología:

// app.js — VULNERABLE: sin cabeceras de seguridad, revela X-Powered-By
const express = require('express');
const app = express();
// Express manda por defecto "X-Powered-By: Express" (pista para el atacante)
app.get('/', (req, res) => res.send('BazarNube API'));

Cada respuesta anuncia X-Powered-By: Express (facilita al atacante buscar CVEs de esa pila) y no incluye ninguna cabecera defensiva. Corrección con helmet, que fija un conjunto de cabeceras sensatas:

// app.js — SEGURO: helmet aplica cabeceras de seguridad y oculta la tecnologia
const helmet = require('helmet');
app.disable('x-powered-by');            // deja de revelar el framework
app.use(helmet());                       // HSTS, X-Content-Type-Options, etc.
app.use(helmet.contentSecurityPolicy({ directives: {
  defaultSrc: ["'self'"], scriptSrc: ["'self'"], frameAncestors: ["'none'"]
}}));
Cabecera Qué hace Frente a
Strict-Transport-Security Fuerza HTTPS Degradación TLS (ver A02)
X-Content-Type-Options: nosniff Impide adivinar el tipo MIME Ataques por sniffing
Content-Security-Policy Restringe orígenes de scripts/recursos XSS (ver 03-04)
X-Frame-Options/frame-ancestors Impide enmarcado Clickjacking
Referrer-Policy Limita el Referer enviado Fuga de URLs

  1. Mensajes de error verbosos y modo desarrollo

En producción, un error debe registrar el detalle en el servidor y devolver al cliente un mensaje genérico. BazarNube tenía lo contrario:

// VULNERABLE: envia el stack trace al cliente
app.use((err, req, res, next) => {
  res.status(500).send(err.stack);   // revela rutas, versiones, estructura
});

Un stack trace expone rutas de ficheros, versiones de librerías, nombres de tablas y a veces fragmentos de consultas: un mapa para el atacante. Corrección:

// SEGURO: log interno detallado, respuesta generica
app.use((err, req, res, next) => {
  logger.error({ err, path: req.path, reqId: req.id }); // detalle SOLO en el log
  res.status(500).json({ error: 'Error interno', reqId: req.id });
});

El reqId permite al soporte correlacionar la incidencia con el log sin revelar nada al cliente. Relacionado: desactiva el modo debug/development de frameworks en producción (NODE_ENV=production), que a menudo activa páginas de error detalladas y desactiva optimizaciones de seguridad.

  1. Cuentas y valores por defecto; superficie innecesaria

  • Cuentas por defecto: paneles de administración, bases de datos y servicios suelen traer admin/admin o similares. Cámbialas siempre; deshabilita las que no uses.
  • Servicios y puertos innecesarios: cada servicio expuesto es superficie de ataque. Si BazarNube no necesita exponer el puerto de PostgreSQL al exterior, no lo publiques.
  • Endpoints de diagnóstico: paneles de métricas, /debug, documentación de API interna... deben estar protegidos o deshabilitados en producción.
  • Directorios y listados: desactiva el directory listing y no sirvas ficheros de configuración (.env, .git).

  1. Hardening de contenedores Docker

BazarNube corre en Docker. Un Dockerfile descuidado es configuración incorrecta de manual:

# Dockerfile — VULNERABLE
FROM node:latest                 # tag flotante: version impredecible
WORKDIR /app
COPY . .                          # copia TODO, incluido .env y .git
RUN npm install
USER root                         # corre como root (por defecto)
CMD ["node", "app.js"]

Problemas: imagen base con tag flotante (latest, imposible de reproducir y a menudo con paquetes de más), copia de secretos y del historial Git a la imagen, y ejecución como root (si alguien escapa del proceso, es root en el contenedor). Versión endurecida:

# Dockerfile — SEGURO
FROM node:20.17-slim             # version fijada y variante slim (menos superficie)
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev            # instalacion reproducible, sin devDependencies
COPY src ./src                    # copiar solo lo necesario (ver .dockerignore)
RUN addgroup --system app && adduser --system --ingroup app app
USER app                          # ejecutar como usuario sin privilegios
CMD ["node", "src/app.js"]

Y un .dockerignore para no filtrar secretos ni el historial:

.git
.env
node_modules
*.log
Práctica Por qué
Tag de versión fijado Reproducibilidad; evita cambios silenciosos
Variante slim/mínima Menos paquetes = menos CVEs (enlaza con A06, 03-08)
USER no root Limita el impacto de un escape del proceso
.dockerignore Evita copiar .env, .git, secretos
Escanear la imagen Detecta vulnerabilidades en capas (SCA de imágenes)

  1. XXE como configuración de parser

El antiguo A04:2017 (XXE) quedó absorbido en A05 porque, en el fondo, es un parser XML mal configurado: viene por defecto con el procesamiento de entidades externas activado, y nadie lo desactivó. Al ser un tema con técnica y explotación propias (lectura de ficheros, SSRF, DoS por "billion laughs"), lo desarrollamos completo en la lección 03-07 – Entidades Externas XML (XXE). Recuerda la relación: XXE es un caso concreto de configuración de seguridad incorrecta.

  1. Proceso: hardening reproducible

La configuración segura no es un ajuste puntual, es un proceso repetible:

flowchart LR
  A[Baseline de hardening] --> B[Config como codigo]
  B --> C[Escaneo automatico en CI]
  C --> D[Mismo entorno dev/staging/prod]
  D --> A
  • Baseline de hardening: una lista de referencia (p. ej. CIS Benchmarks) de cómo debe quedar cada componente.
  • Configuración como código: define la config en ficheros versionados (Dockerfile, IaC), no a mano en cada servidor. Así es auditable y reproducible.
  • Paridad de entornos: dev, staging y producción deben configurarse igual; muchas brechas nacen de un staging "abierto".
  • Escaneo automático: herramientas que revisan cabeceras, config de contenedores e IaC en el pipeline (lo veremos con ZAP en M6 para la parte web).

Errores Comunes y Consejos

  • Desplegar con la config por defecto. Casi siempre prioriza "que arranque" sobre "que sea seguro".
  • Revelar la tecnología (X-Powered-By, cabeceras de versión, stack traces). Ocúltalo.
  • Dejar debug/development en producción. Fija NODE_ENV=production y equivalentes.
  • Contenedores como root y con tag latest. Usa usuario sin privilegios y versiones fijadas.
  • Copiar .env/.git a la imagen. Usa .dockerignore.
  • Configurar a mano cada servidor. Deriva en inconsistencias; usa configuración como código.
  • Consejo: trata la configuración con el mismo rigor que el código: versionada, revisada y escaneada en CI.

Ejercicios

Ejercicio 1. Enumera tres problemas de seguridad en este fragmento de despliegue y corrígelos:

const app = express();
app.use(express.json());
app.use((err, req, res, next) => res.status(500).send(err.stack));
// sin helmet, NODE_ENV sin definir

Ejercicio 2. Un compañero dice: "ocultar X-Powered-By no sirve, el atacante encontrará la tecnología igual". ¿Qué le responderías?

Ejercicio 3. Explica por qué ejecutar el contenedor como root agrava el impacto de otra vulnerabilidad (por ejemplo, una ejecución de comandos como la de A03).

Soluciones

Solución 1. Problemas: (1) no hay cabeceras de seguridad → añadir helmet; (2) el handler de error devuelve el stack al cliente → registrar internamente y responder genérico; (3) NODE_ENV sin definir → fijar production. Además, ocultar X-Powered-By. Ver secciones 2 y 3.

Solución 2. Es cierto que un atacante determinado puede inferir la tecnología por otras vías, pero ocultarla eleva el coste y frena el escaneo automatizado que busca versiones concretas para lanzar exploits conocidos. Es defensa en profundidad: no es la barrera principal, pero suma y no cuesta nada.

Solución 3. Si el proceso corre como root dentro del contenedor y una vulnerabilidad permite ejecutar comandos, esos comandos se ejecutan con privilegios máximos: el atacante puede modificar cualquier fichero del contenedor, instalar herramientas y aumentar la probabilidad de escapar al host. Con un usuario sin privilegios, el radio de daño se reduce notablemente (menor privilegio, defensa en profundidad).

Conclusión

A05 nos recuerda que la seguridad no termina en el código: los valores por defecto, las cabeceras, los errores, las cuentas y los contenedores deben endurecerse de forma explícita, repetible y auditable. En BazarNube añadimos helmet y ocultamos la tecnología, silenciamos los stack traces al cliente, y endurecimos el Dockerfile (versión fijada, usuario no root, .dockerignore). La clave: configuración como código, igual de revisada que la lógica.

Entrada de backlog — A05: helmet + CSP activados y X-Powered-By desactivado; handler de errores genérico con reqId; NODE_ENV=production; Dockerfile endurecido (tag fijado, USER app, .dockerignore); pendiente escaneo de config en CI.

Mencionamos que el XXE es un parser XML mal configurado. El módulo legacy Java de BazarNube procesa XML de facturación, así que es el momento de abrir esa caja. La siguiente lección es Entidades Externas XML (XXE).

Curso de OWASP: Directrices y Estándares para la Seguridad en Aplicaciones Web

Módulo 1: Introducción a OWASP

Módulo 2: Principales Proyectos de OWASP

Módulo 3: OWASP Top Ten 2021 en Profundidad

Módulo 4: OWASP ASVS (Application Security Verification Standard)

Módulo 5: OWASP SAMM (Software Assurance Maturity Model)

Módulo 6: OWASP ZAP (Zed Attack Proxy)

Módulo 7: Buenas Prácticas y Recomendaciones

Módulo 8: Ejercicios Prácticos y Casos de Estudio

Módulo 9: Evaluación y Certificación

© Copyright 2026. Todos los derechos reservados