HexBrain

curl · Report #3973201

CVE-2026-86412: server-set cookie erases cookie jar

Validstatus
14 Oct 2026disclosed

How it was found

Summary

A remote HTTP server can set a cookie that curl accepts and writes to a Netscape cookie jar but cannot reload. On the next invocation using the same path for --cookie and --cookie-jar, curl fails with CURLE_TOO_LARGE (100) before connecting, then cleanup replaces the jar with an empty 131-byte header, deleting cookies for unrelated domains.

The root cause is an inconsistency between what get_netscape_format() can write and what cookie_load() can read, combined with cookie_load() unconditionally marking the store as running even after a load failure, which lets cleanup overwrite the jar from an incomplete store.

Affected versions

curl 8.18.0 through 8.21.0, plus affected 8.22.0 prerelease/development versions.

Root cause

parse_cookie_header() accepts a Set-Cookie value up to MAX_COOKIE_LINE (5000) bytes. get_netscape_format() then adds the domain, path, flags, expiry, and tab delimiters via curl_maprintf with no length check on the serialized record.

A cookie with a 4094-byte name, value v, and Path=/ followed by 870 a characters, set by a 16-byte host (attacker.example), serializes to a 4999-byte jar record.

cookie_load() initializes a dynbuf with MAX_COOKIE_LINE (5000). The dynbuf limit includes the NUL terminator, and Curl_get_line() retains or appends a newline. So the maximum reloadable payload is 4998 bytes before the newline. A 4999-byte record does not fit and Curl_get_line() returns CURLE_TOO_LARGE.

After the error, cookie_load() still sets ci->running = TRUE unconditionally (lib/cookie.c:1211). Curl_pretransfer() propagates the error and never starts a connection, but cleanup sees the cookie store as running and calls cookie_output(), which atomically replaces the configured jar with the partially loaded (empty) store. This bypasses the incomplete-store protection added for issue #18621 because the failed loader has already set the running flag.

Security impact

A remote, unauthenticated HTTP server can:

  1. Prevent all later curl transfers that load the poisoned jar (CURLE_TOO_LARGE before any connection is made).
  2. In the documented same-file --cookie/--cookie-jar workflow, permanently erase cookies belonging to unrelated domains.

The impact is availability loss and persistent-state integrity corruption across domain boundaries. There is no confidentiality loss or memory corruption. Cookie persistence is opt-in and the boundary condition is narrow (one byte), so I would expect Low severity if considered a security issue.

This is adjacent to issue #18621 and the unreloadable TAB-path case fixed by PR #21185, but the effect is broader: one server-controlled cookie prevents unrelated transfers and can erase cross-domain persistent state.

Reproduction

  1. Build an affected curl release, starting with 8.18.0, with HTTP cookie support enabled.
  2. Start two HTTP virtual hosts or test servers representing an attacker-controlled domain and an unrelated victim domain.
  3. Have the victim host set a normal persistent cookie and save it to a Netscape-format cookie jar.
  4. Contact the attacker-controlled host while using the same file as both cookie input and cookie-jar output.
  5. Make the attacker response set a cookie that is accepted by curl's HTTP cookie parser but whose fully serialized Netscape record is one byte larger than the cookie-jar loader can consume.
  6. Construct the boundary using a long but accepted cookie name or value plus a long Path attribute, accounting for the domain, flags, expiry, separators, newline, and terminating NUL added during serialization and reload.
  7. Confirm that curl accepts the attacker cookie, completes the request, and writes a 4999-byte cookie record to the jar alongside the unrelated victim cookie.
  8. Preserve a copy of this poisoned jar for a read-only control and record the number of requests received by the victim server.
  9. Start a new curl transfer to the victim domain with the poisoned file configured as both cookie input and cookie-jar output.
  10. Observe that curl fails with CURLE_TOO_LARGE before connecting because the 4999-byte record, newline, and terminating NUL exceed the loader's 5000-byte dynbuf limit.
  11. Confirm that the victim server received no new request.
  12. Inspect the cookie jar after curl cleanup.
  13. Observe the destructive result: curl has replaced the original jar with only its standard header, deleting the attacker record and all unrelated victim cookies.
  14. Repeat the reload using the preserved copy only as cookie input, without configuring cookie-jar output.
  15. Confirm that loading still fails with CURLE_TOO_LARGE but the input file remains unchanged, isolating the deletion to cleanup persistence.
  16. Create a boundary control in which the attacker cookie serializes to 4998 bytes instead of 4999 bytes.
  17. Repeat the same-file load and save operation and confirm that the shorter record reloads successfully, the victim request occurs, and the cookie jar remains populated.
  18. Confirm a fix by ensuring a failed cookie load does not mark the store as safe to persist and by making accepted in-memory cookie limits consistent with serialized reload limits.
  19. Repeat the 4999-byte test and verify that curl may reject the transfer but never overwrites the original cookie jar from a partially loaded store.

Expected result

A server-set cookie written by curl must either be reloadable or be rejected before persistence. A failed cookie-jar load must never cause cleanup to overwrite the original jar with incomplete state.

Vulnerable result

Curl writes a cookie record that its own loader rejects. The load error occurs before the next connection, but cleanup treats the incomplete store as valid and replaces the shared jar, erasing cookies for every domain in that file.

Suggested fix

Two changes:

  1. Do not set ci->running = TRUE when cookie_load() fails, so cleanup cannot overwrite a jar from an incomplete load.
  2. Make the accepted in-memory cookie limit consistent with the maximum serialized Netscape record that the loader can consume (either check the serialized length in get_netscape_format() or increase the dynbuf limit to account for the added fields).

Impact

Summary:

A remote, unauthenticated server can poison a shared cookie jar so that later curl operations fail before connecting and then erase cookies belonging to unrelated domains. This can destroy persistent authentication and application state, log users out, disrupt automated workflows, or alter subsequent request behavior; it does not directly disclose cookies or corrupt memory.

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