HexBrain

curl · Report #3972395

CVE-2026-80255: secure cookie attribute bypass with tab

Validstatus
1upvotes
3 Sept 2026disclosed

How it was found

Summary

A Set-Cookie header using HTAB (horizontal tab) instead of SPACE before the Secure attribute — e.g. Set-Cookie: sid=SECRET;\tSecure — causes curl to store the cookie without its Secure flag. The cookie is then sent over plaintext HTTP on subsequent requests to the same host.

The space-separated form Set-Cookie: sid=SECRET; Secure works correctly.

Affected versions

curl 8.13.0 through at least 8.21.0 (all versions containing commit 1aea05a6c26, "cookie: convert to using strparse", 2025-02-18).

Root cause

In lib/cookie.c, parse_cookie_header() uses a do/while loop that parses each cookie attribute:

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

After consuming the ; separator, ptr points at the HTAB. Since \t is in the reject set of curlx_str_cspn, the function sees it as the first character, returns an empty match (failure), and the if block is skipped. Then curlx_str_single(&ptr, ';') fails because the current byte is \t, not ;, so the loop exits. The Secure attribute is never parsed, but the cookie is still stored.

The previous parser (before 1aea05a6c26) had while(ISBLANK(*ptr)) ptr++; before parsing each attribute, which correctly skipped HTAB. This blank-skipping was lost in the strparse conversion. The helper curlx_str_passblanks() exists in the codebase but is not called here.

Reproduction

  1. Build curl at commit c2676bf9e639e31e44b12a2cc82cf2046231b056 (or any version from 8.13.0 onward) with OpenSSL support enabled.

  2. Generate a self-signed TLS certificate for the hostname test.invalid, valid for at least one day.

  3. Start a local server that listens on two ports:

HTTPS on port 8443 — serves Set-Cookie: sid=HTAB_SECRET;\tSecure (with a literal HTAB character 0x09 before Secure) when the path is /htab, and Set-Cookie: sid=SPACE_SECRET; Secure (with a normal space) when the path is /space. HTTP on port 8080 — echoes back the received Cookie header value in the response body.

  1. Set the HTAB cookie over HTTPS by requesting https://test.invalid:8443/htab, saving the cookie jar:
curl -ks --noproxy '*' \
  --resolve test.invalid:8443:127.0.0.1 \
  -c /tmp/htab.jar \
  https://test.invalid:8443/htab
Make a follow-up request over plaintext HTTP using the saved cookie jar:
curl -sS --noproxy '*' \
  --resolve test.invalid:8080:127.0.0.1 \
  -b /tmp/htab.jar \
  http://test.invalid:8080/
  1. Observe that the cookie is sent over HTTP — the response body will contain sid=HTAB_SECRET, proving the Secure flag was silently dropped and the cookie leaked over plaintext.

  2. Inspect the cookie jar — the fourth field (Secure flag) for the HTAB cookie will be FALSE, confirming curl did not store the Secure attribute.

  3. Run the control case — repeat steps 4-5 using the /space endpoint instead, which sets the cookie with a normal space before Secure. The follow-up HTTP request will receive <none> — the cookie is correctly withheld. The cookie jar will show the Secure flag as TRUE.

Suggested fix

Add curlx_str_passblanks(&ptr); after the ; is consumed in the do/while loop, before curlx_str_cspn parses the next attribute name. Add a regression test comparing ;\tSecure against ; Secure and verifying neither cookie is returned for an HTTP URL.

Relationship to existing work

This is distinct from PR #21185 / test 1685 (commit 8e8bdd3604), which addresses HTAB inside a cookie attribute value (e.g. Path=/foo\tbar). This issue concerns HTAB before an attribute name, causing the attribute to be silently discarded.

Impact

Summary:

The Secure flag is a confidentiality boundary that prevents cookies from being sent over unencrypted connections. Silently dropping it means a session cookie intended to be HTTPS-only is exposed on any subsequent HTTP request to the same host. A network attacker observing that HTTP traffic can capture the cookie. All attributes at or after the HTAB position are similarly skipped (e.g. HttpOnly).

HTAB is valid optional whitespace per both RFC 6265 section 5.2 and the HTTP grammar (OWS). The old parser handled it correctly.

This report was disclosed publicly on HackerOne and belongs to its author and the program. See the original