Reports
MEDIA[OAUTH Y SESIONES]#3972395

curl pierde el atributo Secure de una cookie si va precedido de un tabulador (CVE-2026-80255)

Desde curl 8.13.0, un Set-Cookie con tabulador antes de Secure hace que la cookie se guarde sin ese atributo, y curl la envía después en claro por HTTP al mismo host.

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 analizador de cookies de curl (parse_cookie_header() en lib/cookie.c) deja de leer los atributos de una cookie cuando, tras el ; separador, encuentra un tabulador horizontal (HTAB, 0x09) en lugar de un espacio. La cookie se guarda igualmente, pero sin los atributos que venían a partir de ese punto. Si el que se pierde es Secure, curl acabará enviando esa cookie por HTTP sin cifrar en las siguientes peticiones al mismo host.

La diferencia entre la cabecera que funciona y la que no es un solo carácter:

- Set-Cookie: sid=SECRET; Secure
+ Set-Cookie: sid=SECRET;\tSecure

Con el espacio, la cookie se marca como segura. Con el tabulador, se almacena sin la marca Secure.

Datos del caso:

  • Programa: curl
  • CVE: CVE-2026-80255
  • Debilidad declarada: Improper Input Validation
  • Versiones afectadas: de curl 8.13.0 hasta al menos 8.21.0, es decir, todas las que incluyen el commit 1aea05a6c26 ("cookie: convert to using strparse", 18 de febrero de 2025).
  • Enviado: 26 de agosto de 2026. Divulgado: 3 de septiembre de 2026.

Por qué ocurre

El bucle que recorre los atributos tiene esta forma:

do {
    if(!curlx_str_cspn(&ptr, &name, ";\t\r\n=")) {
        // parse attribute
    }
} while(!curlx_str_single(&ptr, ';'));

La secuencia es la siguiente:

  1. Se consume el ; y ptr queda apuntando al tabulador.
  2. curlx_str_cspn tiene \t en su conjunto de caracteres rechazados. Como es el primer carácter, devuelve una coincidencia vacía (fallo) y el bloque if no se ejecuta.
  3. curlx_str_single(&ptr, ';') también falla, porque el byte actual es \t y no ;. El bucle termina.
  4. Secure nunca llega a analizarse, pero la cookie ya está aceptada y se guarda.

Antes de la migración a strparse, el analizador ejecutaba while(ISBLANK(*ptr)) ptr++; antes de cada atributo, y eso saltaba tanto espacios como tabuladores. Con la conversión se perdió ese paso, aunque en el código ya existe una función que lo hace, curlx_str_passblanks(), que aquí no se llamaba.

El investigador aclara que el fallo es distinto del que corrigió el PR #21185 / test 1685 (commit 8e8bdd3604). Aquel trataba tabuladores dentro del valor de un atributo (por ejemplo, Path=/foo\tbar). Este afecta a un tabulador antes del nombre del atributo, lo que hace que el atributo se descarte sin avisar.

Pasos de reproducción

  1. Compila curl en el commit c2676bf9e639e31e44b12a2cc82cf2046231b056 (vale cualquier versión desde la 8.13.0) con soporte OpenSSL.
  2. Genera un certificado TLS autofirmado para el nombre test.invalid, válido al menos un día.
  3. Levanta un servidor local que escuche en dos puertos:
  • HTTPS en el 8443: en la ruta /htab responde con Set-Cookie: sid=HTAB_SECRET;\tSecure (con un tabulador real, 0x09, antes de Secure). En la ruta /space responde con Set-Cookie: sid=SPACE_SECRET; Secure (con un espacio normal).
  • HTTP en el 8080: devuelve en el cuerpo de la respuesta el valor de la cabecera Cookie que recibe.
  1. Pide la ruta /htab por HTTPS y guarda la cookie en un fichero:
curl -ks --noproxy '*' \
  --resolve test.invalid:8443:127.0.0.1 \
  -c /tmp/htab.jar \
  https://test.invalid:8443/htab
  1. Haz una segunda petición, ahora por HTTP en claro, usando ese mismo fichero de cookies:
curl -sS --noproxy '*' \
  --resolve test.invalid:8080:127.0.0.1 \
  -b /tmp/htab.jar \
  http://test.invalid:8080/
  1. En el cuerpo de la respuesta aparece sid=HTAB_SECRET. curl ha enviado por HTTP una cookie que el servidor había marcado como Secure.
  2. Abre /tmp/htab.jar. En la línea de esa cookie, el cuarto campo (la marca Secure) vale FALSE, lo que confirma que curl no guardó el atributo.
  3. Como control, repite los pasos 4 y 5 con la ruta /space. Ahora la petición HTTP recibe <none>, porque curl retiene la cookie como corresponde, y el fichero muestra la marca Secure a TRUE.

Impacto

La marca Secure existe precisamente para que una cookie no salga nunca por una conexión sin cifrar. Si curl la descarta, una cookie de sesión que el servidor quería limitar a HTTPS se envía en cualquier petición HTTP posterior al mismo host. Un atacante que pueda observar ese tráfico de red puede capturarla.

El problema no se limita a Secure: se ignoran todos los atributos situados en la posición del tabulador o después, como HttpOnly.

El investigador recuerda además que el tabulador es espacio en blanco opcional válido según la sección 5.2 del RFC 6265 y la gramática HTTP (OWS). Es decir, un servidor que lo use está cumpliendo el estándar, y el analizador anterior de curl lo aceptaba sin problemas.

Remediación

El reporte figura como resuelto en HackerOne, aunque no detalla el parche aplicado finalmente ni la versión corregida. La corrección que propuso el investigador es:

  • Llamar a curlx_str_passblanks(&ptr); dentro del bucle, justo después de consumir el ; y antes de que curlx_str_cspn lea el nombre del siguiente atributo.
  • Añadir un test de regresión que compare ;\tSecure con ; Secure y compruebe que ninguna de las dos cookies se devuelve para una URL HTTP.