OWASP A05 Configuración Insegura
El 90% de las aplicaciones tiene alguna configuración insegura. Aprende qué cubre OWASP A05:2021, ve el código vulnerable vs. seguro en Next.js, y corrígelo ya.
Rod
Founder & Developer
La configuración insegura de OWASP es el hallazgo más común en cualquier benchmarking de seguridad de aplicaciones. Los datos propios de OWASP — evaluando aplicaciones reales — lo ubican en el 90%: nueve de cada diez apps tienen al menos una configuración insegura. Subió del #6 al #5 en el OWASP Top 10 2021 y absorbió XXE (XML External Entity injection), que en 2017 era su propia categoría separada.
Eso dice algo importante: las configuraciones inseguras no son casos raros. Son el estado por defecto de las apps que no han sido endurecidas explícitamente.
La buena noticia es que la mayoría son fáciles de corregir. Lo difícil es saber que existen.
Qué cubre realmente OWASP A05:2021
Configuración Insegura es una categoría amplia. OWASP la define como cualquier caso donde la seguridad pudo haberse configurado correctamente pero no lo fue. Incluye:
- Cabeceras de seguridad faltantes o mal configuradas
- CORS demasiado permisivo
- Credenciales por defecto activas
- Mensajes de error verbosos que exponen internos del servidor
- Funcionalidades, servicios o puertos innecesarios habilitados
- Source maps o endpoints de debug accesibles en producción
- Parsers XML con procesamiento de entidades externas habilitado (XXE)
- Documentación de API o interfaces de administración expuestas públicamente
- Introspección de GraphQL habilitada en producción
El hilo común: todo esto está deshabilitado en una app correctamente endurecida, pero habilitado por defecto en la mayoría de los frameworks y plataformas de hosting.
Lo que la mayoría de los artículos no menciona: Vercel, Railway, Render y Fly deployean tu app tal como está. No agregan cabeceras de seguridad. No deshabilitan la introspección de GraphQL. No eliminan los source maps. Eso es tu responsabilidad.
En Data Hogo escaneamos más de 50 repositorios en distintos stacks. Cabeceras de seguridad faltantes aparecieron en el 94% de ellos. CORS abierto en API routes apareció en el 31%. Source maps en producción aparecieron en el 27%. No son errores raros — son la línea base.
Las 9 configuraciones inseguras más comunes (con correcciones)
1. Cabeceras de seguridad faltantes
Las cabeceras de seguridad HTTP son instrucciones que le mandas al navegador para decirle cómo comportarse al cargar tu sitio. Sin ellas, los navegadores usan defaults permisivos que exponen a tus usuarios a clickjacking, MIME sniffing, cross-site scripting y ataques de downgrade de protocolo.
Las cabeceras que más importan:
| Cabecera | Qué previene |
|---|---|
Content-Security-Policy |
XSS — restringe qué scripts pueden ejecutarse |
X-Frame-Options |
Clickjacking — bloquea que tu página sea embebida en iFrames |
Strict-Transport-Security |
Downgrade de protocolo — fuerza HTTPS después de la primera visita |
X-Content-Type-Options |
MIME sniffing — fuerza al navegador a respetar el Content-Type declarado |
Referrer-Policy |
Filtración de URLs sensibles a terceros vía el header Referer |
Permissions-Policy |
Restringe acceso a APIs del navegador (cámara, micrófono, geolocalización) |
Ninguna de estas es agregada por Next.js o Vercel por defecto. Las agregas en next.config.ts:
// next.config.ts — agrega esta función headers()
import type { NextConfig } from "next";
const securityHeaders = [
{
key: "X-Frame-Options",
value: "SAMEORIGIN", // bloquea que tu sitio sea embebido en iframes
},
{
key: "X-Content-Type-Options",
value: "nosniff", // el navegador debe confiar en el Content-Type declarado, no adivinar
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload", // 2 años, solo HTTPS
},
{
key: "Referrer-Policy",
value: "strict-origin-when-cross-origin",
},
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=()", // denegar a menos que sea necesario
},
{
key: "Content-Security-Policy",
// Empieza con una política estricta, relaja solo lo que tu app realmente necesita
value: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';",
},
];
const nextConfig: NextConfig = {
async headers() {
return [
{
source: "/(.*)", // aplica a todas las rutas
headers: securityHeaders,
},
];
},
};
export default nextConfig;Revisa tus cabeceras actuales gratis con el verificador de cabeceras de Data Hogo — pega tu URL y obtén una calificación al instante.
Para un análisis más profundo de cada cabecera y cómo configurar CSP específicamente para Next.js, revisa también nuestra entrada de enciclopedia sobre Content-Security-Policy faltante y X-Frame-Options faltante.
2. CORS con wildcard
CORS (Cross-Origin Resource Sharing) controla qué sitios web pueden hacer peticiones a tu API desde un navegador. Cuando configuras Access-Control-Allow-Origin: *, le dices al navegador: "Cualquier sitio en internet puede llamar a este endpoint."
Para una API pública y de solo lectura que sirve datos no sensibles, está bien. Para cualquier API route que maneje autenticación, datos de usuarios o mutaciones, es una vulnerabilidad. Permite que un sitio malicioso haga peticiones a tu API en nombre de tus usuarios que tengan sesión activa, sin que ellos lo sepan.
// MAL: app/api/user/route.ts — wildcard permite cualquier origen
export async function GET(req: Request) {
return Response.json(
{ user: await getCurrentUser() },
{
headers: {
"Access-Control-Allow-Origin": "*", // cualquier sitio puede llamar esto por tus usuarios
},
}
);
}// BIEN: restringe a tu propio dominio (o una lista de orígenes confiables)
const ALLOWED_ORIGINS = [
"https://datahogo.com",
"https://app.datahogo.com",
];
export async function GET(req: Request) {
const origin = req.headers.get("origin") ?? "";
const allowedOrigin = ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
return Response.json(
{ user: await getCurrentUser() },
{
headers: {
"Access-Control-Allow-Origin": allowedOrigin, // allowlist explícita
"Vary": "Origin", // le dice a los CDNs que cacheen por origen
},
}
);
}Ve el análisis completo en la entrada de enciclopedia sobre CORS abierto en API routes.
3. Source maps en producción
Los source maps son archivos que mapean tu JavaScript minificado y compilado de vuelta a tu código fuente original. Son esenciales en desarrollo — hacen legibles los mensajes de error. En producción, le entregan a un atacante una copia de tu código fuente sin minificar, incluyendo lógica, rutas de endpoints y comentarios internos que preferirías mantener privados.
// MAL: next.config.ts — habilitando explícitamente source maps en producción
const nextConfig: NextConfig = {
productionBrowserSourceMaps: true, // tu código fuente ahora es público
};// BIEN: source maps desactivados en producción (default de Next.js), activos en dev
const nextConfig: NextConfig = {
// productionBrowserSourceMaps tiene default false — no lo cambies
// Si necesitas contexto de errores en producción, usa la carga privada de source maps de Sentry
};Si necesitas stack traces legibles en el reporte de errores de producción (deberías), usa la carga de source maps de Sentry — los sube a los servidores de Sentry, no a tu CDN público, así solo tu herramienta de monitoreo puede leerlos.
La entrada dedicada sobre source maps expuestos en producción explica cómo los usan los atacantes y qué revisar en tu build.
4. Mensajes de error verbosos
Cuando tu servidor lanza una excepción no manejada y responde algo así:
Error: Cannot read properties of undefined (reading 'user')
at /app/api/orders/route.ts:47:23
at processTicksAndRejections (node:internal/process/task_queues:95:5)...le acabas de regalar al atacante la estructura de tu sistema de archivos, la línea exacta que falló y una pista sobre tu modelo de datos. Las aplicaciones bajo ataque activo usan mensajes de error como este para mapear la superficie de la aplicación.
// MAL: regresando errores crudos al cliente
export async function GET(req: Request) {
try {
const data = await fetchOrders();
return Response.json(data);
} catch (error) {
// manda el stack trace y la ruta interna al navegador
return Response.json({ error: (error as Error).message }, { status: 500 });
}
}// BIEN: mensaje genérico al cliente, detalles completos a tu sistema de logging
import * as Sentry from "@sentry/nextjs";
export async function GET(req: Request) {
try {
const data = await fetchOrders();
return Response.json(data);
} catch (error) {
// loguea el error real internamente — Sentry, logs estructurados, lo que uses
Sentry.captureException(error);
// no mandes nada útil al cliente
return Response.json(
{ error: "Algo salió mal. Por favor intenta de nuevo." },
{ status: 500 }
);
}
}La regla: loguea los detalles en el servidor, manda mensajes genéricos al cliente. Sin excepciones. Lee más sobre mensajes de error verbosos como vulnerabilidad.
5. Credenciales por defecto activas
Suena obvio, pero esta es la causa de brechas masivas en el mundo real. Bases de datos, paneles de administración, dashboards de monitoreo, sistemas de CI y colas de mensajes vienen con usuarios y contraseñas por defecto. Si instalas algo y no cambias las credenciales por defecto, cualquier persona que conozca los defaults del software puede entrar.
Los ejemplos más comunes:
- MongoDB no requería autenticación por defecto hasta la versión 3.0
- Redis no tiene autenticación por defecto
- Grafana tiene default
admin/admin - Jenkins tiene
admincon contraseña en/var/lib/jenkins/secrets/initialAdminPassword(a veces accesible) - phpMyAdmin expuesto en
/phpmyadmincon credenciales MySQL por defecto
La corrección es simple: cambia los defaults inmediatamente después de la instalación, antes de que el servicio sea accesible desde cualquier red. Usa contraseñas únicas y fuertes. Desactiva el usuario admin por defecto si el software permite crear cuentas admin separadas.
6. Introspección de GraphQL en producción
La introspección de GraphQL te permite consultar la API para descubrir todo su schema — cada tipo, cada query, cada mutation, cada campo. Es indispensable en desarrollo. En producción, es una herramienta de reconocimiento gratuita para cualquiera que quiera sondear tu API.
Con la introspección habilitada, un atacante puede:
- Mapear todos los queries y mutations disponibles
- Descubrir campos que tu UI no expone pero tu API todavía acepta
- Encontrar campos deprecated que pueden carecer de autorización adecuada
- Entender tu modelo de datos lo suficiente para construir ataques dirigidos
// MAL: Apollo Server con introspección habilitada en producción
import { ApolloServer } from "@apollo/server";
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true, // cualquiera puede consultar tu schema completo
});// BIEN: deshabilita la introspección según el entorno
const server = new ApolloServer({
typeDefs,
resolvers,
// introspection tiene default true en desarrollo, false en producción
// pero sé explícito:
introspection: process.env.NODE_ENV !== "production",
});La entrada de enciclopedia sobre introspección de GraphQL habilitada en producción explica qué puede hacer un atacante con tu schema y cómo verificar que esté deshabilitada.
7. Rutas de debug en producción
Muchos frameworks y librerías agregan endpoints de debug durante el desarrollo. El error-handling middleware de Express muestra stack traces completos. Rails tiene /rails/info/properties. Algunas apps tienen rutas /debug, /health o /__internal que se agregaron por conveniencia y nunca se eliminaron antes de deployar.
Estas rutas exponen estado interno, variables de entorno, versiones de dependencias y logs de requests. Revisa tu configuración de rutas antes de cada deploy a producción.
La entrada sobre rutas de debug en producción tiene un checklist de patrones para los frameworks más comunes.
8. XXE — XML External Entity Injection
XXE ocurre cuando una aplicación parsea XML y el parser tiene habilitado el procesamiento de entidades externas. Un documento XML puede definir entidades que referencian recursos externos:
<!-- MAL: un payload XML controlado por un atacante -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<data>&xxe;</data>Cuando tu servidor parsea esto, &xxe; se reemplaza con el contenido de /etc/passwd. El atacante obtiene la base de datos de usuarios de tu servidor en el output parseado.
La corrección en la mayoría de los lenguajes es una sola bandera de configuración:
// Ejemplo usando fast-xml-parser (Node.js)
import { XMLParser } from "fast-xml-parser";
// MAL: la config por defecto puede permitir expansión de entidades
const parser = new XMLParser();
// BIEN: previene explícitamente el procesamiento de entidades
const parser = new XMLParser({
processEntities: false, // deshabilita el procesamiento de entidades externas
allowBooleanAttributes: true,
});OWASP fusionó XXE en A05:2021 porque la causa raíz siempre es un parser mal configurado — uno que viene con un default peligroso. Lee la explicación completa en XXE — XML External Entity injection.
9. Puertos y servicios innecesarios abiertos
Cada puerto abierto es superficie de ataque. Si tu servidor corre una base de datos, un caché, un dashboard de administración y tu aplicación — y todos son accesibles por red — tu superficie de ataque es cuatro veces lo que necesita ser.
El principio de instalación mínima: corre solo lo que necesitas, expónlo solo a las redes que lo necesitan, y cierra todo lo demás.
Para deployments en la nube: usa security groups y reglas de firewall para restringir el acceso a la base de datos solo a tus servidores de aplicación. Redis y PostgreSQL nunca deben ser accesibles desde internet público. Las interfaces de administración deben estar detrás de una VPN o bastion host, no en una IP pública.
Un caso real: Microsoft Power Apps (2021)
En agosto de 2021, las tablas del portal de Microsoft Power Apps estaban mal configuradas por defecto — eran accesibles públicamente sin autenticación. Investigadores de UpGuard descubrieron que 38 millones de registros de 47 entidades (incluyendo American Airlines, Ford y varios gobiernos estatales) quedaron expuestos porque la configuración por defecto de los portales Power Apps permitía acceso público a las tablas.
No fue un exploit de zero-day. No fue un ataque sofisticado. Fue una configuración por defecto que no se cambió. Los datos incluían estatus de vacunación por COVID-19, direcciones de correo de empleados y números de Seguro Social.
La respuesta de Microsoft fue cambiar el default y proveer una herramienta de escaneo para identificar portales mal configurados. Pero el daño — exposición de 38 millones de registros — ocurrió por un default que venía permisivo.
Esto es lo que OWASP A05 significa en la práctica. No un bypass sofisticado, solo una configuración que debía estar apagada y no lo estaba.
Checklist de hardening para apps Next.js
Revisa esto antes de cada deploy a producción:
Cabeceras
-
Content-Security-Policyconfigurado (hasta undefault-src 'self'básico es mejor que nada) -
X-Frame-Options: SAMEORIGIN— o usaframe-ancestorsen tu CSP -
X-Content-Type-Options: nosniff -
Strict-Transport-Securityconpreloady al menos 1 año demax-age -
Referrer-Policy: strict-origin-when-cross-origin - Revisa tus cabeceras gratis con el verificador de cabeceras de seguridad
CORS
- Ninguna API route retorna
Access-Control-Allow-Origin: *para endpoints autenticados - La allowlist de orígenes CORS usa coincidencia exacta de dominio, no
endsWith()ni substring
Build
-
productionBrowserSourceMapsno está entrueennext.config.ts - Los source maps se suben a Sentry (o equivalente) si necesitas contexto de errores en producción
Manejo de errores
- Ninguna API route retorna
error.messageoerror.stackcrudo al cliente - Las respuestas de error usan mensajes genéricos; los detalles van a logs del servidor
Servicios
- Introspección de GraphQL deshabilitada en producción
- Sin rutas de debug o internas accesibles en producción
- Sin credenciales por defecto en ningún servicio del que depende tu app
- La base de datos y el caché no son accesibles públicamente (security groups / reglas de firewall)
XML (si aplica)
- El parser XML está configurado para deshabilitar el procesamiento de entidades externas
Prevención: automatiza lo que puedas
El checklist de hardening de arriba es útil. Pero los checklists manuales fallan a escala — se pasan cosas, se agregan features, las dependencias cambian. El escaneo automatizado de configuración detecta configuraciones inseguras de forma continua, no solo al momento del deploy.
La entrada de enciclopedia sobre configuración insegura cubre la categoría completa con referencias a OWASP y mapeos a CWE.
Para Next.js específicamente, Data Hogo escanea tu repositorio en busca de todas las configuraciones inseguras cubiertas en este post: cabeceras faltantes (detectadas en tu next.config.ts), CORS con wildcard en API routes, source maps habilitados en producción, manejo de errores verboso e introspección de GraphQL. También escanea tu URL deployada para la configuración de cabeceras en vivo.
Escanea tu repo gratis → — los primeros 3 escaneos son gratis, sin tarjeta de crédito. O revisa tus cabeceras en vivo ahora con el verificador gratuito.
Preguntas Frecuentes
¿Qué es OWASP A05:2021 Configuración Insegura?
OWASP A05:2021 Configuración Insegura cubre vulnerabilidades que surgen de ajustes de seguridad incorrectos o incompletos: cabeceras HTTP faltantes, CORS demasiado permisivo, mensajes de error verbosos, credenciales por defecto y más. Subió del #6 al #5 en el OWASP Top 10 de 2021 y absorbió XXE (XML External Entity), que en 2017 era su propia categoría separada. Según los datos de OWASP, el 90% de las aplicaciones evaluadas tenían alguna configuración insegura.
¿Cuáles son las configuraciones inseguras más comunes en Next.js?
Las más comunes en apps de Next.js son: cabeceras de seguridad faltantes (CSP, X-Frame-Options, HSTS, X-Content-Type-Options), CORS con wildcard (*) en API routes, source maps habilitados en producción, mensajes de error verbosos que exponen stack traces a los usuarios, y la introspección de GraphQL activa en producción. Todas son detectables con escaneo automatizado.
¿Cómo agrego cabeceras de seguridad a una app de Next.js?
Agrega una función headers() a tu archivo next.config.ts. Retorna un arreglo de objetos con source (el patrón de URL) y headers (las cabeceras a agregar). Las cabeceras mínimas recomendadas son Content-Security-Policy, X-Frame-Options: SAMEORIGIN, X-Content-Type-Options: nosniff, Strict-Transport-Security, Referrer-Policy y Permissions-Policy. El post incluye el config completo listo para copiar.
¿CORS con * es siempre una vulnerabilidad?
Depende del contexto. Para una API pública que sirve datos a cualquier origen — como un CDN público o un dataset abierto — el wildcard de CORS es apropiado. Para cualquier API route que maneje datos de usuarios autenticados, información de cuenta o mutaciones, el wildcard de CORS es una vulnerabilidad. Permite que cualquier sitio web haga peticiones a tu API en nombre de un usuario que tenga sesión activa, habilitando cross-site request forgery.
¿Qué es XXE y por qué ahora forma parte de OWASP A05?
XXE (XML External Entity injection) ocurre cuando una aplicación procesa XML con un parser que tiene habilitado el procesamiento de entidades externas. Un atacante puede referenciar archivos del servidor como /etc/passwd o disparar server-side request forgery. OWASP fusionó XXE en A05:2021 Configuración Insegura porque la causa raíz siempre es un parser XML mal configurado — uno que tiene el procesamiento de entidades externas habilitado por defecto.
Posts Relacionados
OWASP A03 Ataques de Inyección
OWASP A03:2021 Inyección cubre SQL, NoSQL, XSS, inyección de comandos y más. Código vulnerable vs. seguro en Node.js, un breach real, y cinco estrategias de prevención.
OWASP A01 Control de Acceso Roto
El control de acceso roto es el riesgo #1 del OWASP Top 10. Esta guía explica IDOR, JWT tampering y rutas sin auth — con ejemplos reales en Next.js y cómo corregirlos.
OWASP A04 Diseño Inseguro
OWASP A04:2021 Diseño Inseguro no se trata de código con bugs — son fallas de arquitectura y lógica de negocio. Aprende a detectarlas con ejemplos de código reales.