Reports
MEDIA[OAUTH Y SESIONES]#3972385

curl con libpsl deja escapar cookies de un sufijo público a hosts hermanos (CVE-2026-82209)

Si un host que es sufijo público (como github.io) fija una cookie con Domain igual a sí mismo, curl la guarda con alcance de dominio y la envía a cualquier subdominio hermano, como attacker.github.io.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

El fallo, registrado como CVE-2026-82209, está en la gestión de cookies de curl (lib/cookie.c) cuando se compila con soporte de libpsl, la biblioteca que aplica la Public Suffix List (PSL). Esa lista marca dominios como github.io, bajo los que cada subdominio pertenece a un dueño distinto, y sirve precisamente para que una cookie no pueda compartirse entre ellos.

El caso problemático es muy concreto: el propio host que responde es un sufijo público (por ejemplo, github.io) y envía un Set-Cookie cuyo atributo Domain coincide exactamente con él. Según la RFC 6265 (§5.3), en ese caso la cookie debe tratarse como host-only, es decir, solo debe volver a enviarse a ese mismo host. curl, en cambio, la almacena con alcance de dominio, así que la adjunta a cualquier host hermano bajo el mismo sufijo (attacker.github.io, por ejemplo) en una petición posterior o al seguir una redirección.

Según el reporte, el código vulnerable está al menos en curl 8.21.0 (la última versión estable en el momento del reporte) y en el árbol 8.22.0-DEV, y la lógica de parse_domain() lleva sin cambios muchas versiones. Solo afecta a compilaciones con libpsl (--with-libpsl), algo habitual en los paquetes de las distribuciones.

Dónde está el error

parse_domain() marca como tailmatch (coincidencia por sufijo) cualquier cookie con un atributo Domain explícito que no sea una IP:

if(!is_ip)
  co->tailmatch = TRUE;

Después, is_public_suffix() hace esta llamada:

psl_is_cookie_domain_acceptable(psl, "github.io", "github.io")

libpsl responde "aceptable" a propósito cuando el host y el dominio son iguales, porque cuenta con que quien la llama convierta la cookie en host-only. curl nunca hace esa conversión.

Por último, al construir la cabecera Cookie de salida, Curl_cookie_getlist() llama a cookie_tailmatch("github.io", 9, "attacker.github.io"). Como attacker.github.io termina en .github.io, la función devuelve TRUE y la cookie se envía al host hermano.

Pasos de reproducción

  1. Compila curl en el commit c2676bf9e639e31e44b12a2cc82cf2046231b056 (o cualquier versión desde la 8.21.0) en Linux o macOS de 64 bits, con libpsl activado (--with-libpsl o -DCURL_USE_LIBPSL=ON). Comprueba que la salida de curl --version incluye libpsl.
  2. Levanta un servidor HTTP local en el puerto 8080 con dos rutas:
  • /set-domain: responde 302, fija la cookie siguiente y redirige a http://attacker.github.io:8080/sink.

`` Set-Cookie: sid=SECRET; Domain=github.io; Path=/ ``

  • /sink: responde 200 y devuelve en el cuerpo el valor de la cabecera Cookie recibida.
  1. Haz que los dos nombres apunten a localhost con la opción --resolve de curl:

`` github.io:8080:127.0.0.1 attacker.github.io:8080:127.0.0.1 ``

  1. Lanza la petición vulnerable siguiendo redirecciones (-L) y con un fichero de cookies (-c /tmp/domain.jar):

`` curl -sS -L --noproxy '*' \ --resolve github.io:8080:127.0.0.1 \ --resolve attacker.github.io:8080:127.0.0.1 \ -c /tmp/domain.jar \ http://github.io:8080/set-domain ``

  1. El cuerpo de la respuesta contiene sid=SECRET: la cookie ha llegado a attacker.github.io, el destino de la redirección y host hermano bajo el mismo sufijo público.
  2. En /tmp/domain.jar, la entrada aparece como .github.io con el indicador de subdominios a TRUE, lo que confirma que se guardó con alcance de dominio.
  3. Primer control negativo: repite el flujo, pero con el servidor fijando la cookie sin atributo Domain:

`` Set-Cookie: sid=HOST_ONLY; Path=/ ``

El destino attacker.github.io recibe <none> y el fichero de cookies muestra github.io con el indicador de subdominios a FALSE (host-only). Es el comportamiento correcto.

  1. Segundo control negativo: emite la misma cookie Domain=github.io desde un host hermano (tenant.github.io) en lugar del propio github.io. curl la rechaza como debe: el fichero de cookies queda vacío y no se envía nada al destino de la redirección.

Impacto

Quien controle un subdominio bajo un sufijo público puede recibir las cookies que el propio sufijo haya emitido con un Domain igual a sí mismo. Conseguir ese subdominio es trivial en plataformas de alojamiento como GitHub Pages, Heroku o Netlify. Se rompe así la frontera entre dueños que libpsl debería hacer cumplir.

Tiene límites: el atacante no puede plantar la cookie. Para explotarlo hace falta que el host del sufijo público emita una cookie de ese tipo y que el cliente curl contacte después con el subdominio del atacante.

Remediación

El investigador propone que, cuando psl_is_cookie_domain_acceptable() acepte la coincidencia exacta entre el host de la petición y un dominio que es sufijo público, se desactive tailmatch para que la cookie pase a ser host-only. Los demás intentos de fijar Domain a un sufijo público se seguirían rechazando. En concreto, tras la comprobación PSL en Curl_cookie_add():

if(co->tailmatch && domain && co->domain &&
   curl_strequal(co->domain, domain) &&
   psl_is_public_suffix(psl, lcookie))
  co->tailmatch = FALSE;

El reporte figura como resuelto, pero no detalla qué cambio aplicó finalmente el equipo de curl. Se envió el 26 de agosto de 2026 y se divulgó el 3 de septiembre de 2026.