Description
I am reporting on some inconsistencies discovered while researching browser/server compliance with rfc6265bis.
rfc6265bis-04 changed the cookie parsing algorithm to support nameless cookies, i.e., Set-Cookie: token
should create a nameless cookie with value token
. The commit that introduced the change is 0178223, which followed the discussion at #159.
The relevant parts are in Section 5.4: The Set-Cookie Header Field, point 3.
If the name-value-pair string lacks a %x3D ("=") character, then
the name string is empty, and the value string is the value of
name-value-pair.
Otherwise, the name string consists of the characters up to, but
not including, the first %x3D ("=") character, and the (possibly
empty) value string consists of the characters after the first
%x3D ("=") character.
And in Section 5.6.3: Retrieval Algorithm, point 4.
- If the cookies' name is not empty, output the cookie's name
followed by the %x3D ("=") character.- If the cookies' value is not empty, output the cookie's
value.- If there is an unprocessed cookie in the cookie-list, output
the characters %x3B and %x20 ("; ").
Conflict with the BNF syntax
These changes seem to be incompatible with the Set-Cookie BNF syntax in Section 4.1.1, where the =
symbol is considered mandatory in the cookie-pair
definition: cookie-pair = cookie-name BWS "=" BWS cookie-value
.
Inconsistent browser behavior
According to my tests, recent versions of Chrome and Firefox follow the latest rfc6265bis, while Safari deviates from the intended behavior. Setting a cookie via document.cookie = "test"
on example.com
results into the following:
Browser | Cookie Name | Cookie Value | document.cookie getter |
HTTP request header |
---|---|---|---|---|
Chrome | test |
'test' |
Cookie: test |
|
Firefox | test |
'test' |
Cookie: test |
|
Safari | test |
'test=' |
Cookie: test= |
Client/Server confusion: cookie tossing
Major server-side programming languages and web development frameworks are not compatible with nameless cookies as specified in rfc6265bis. For instance, PHP and Flask-based applications would treat the request header Cookie: test
as a cookie named test
with an empty value.
This discrepancy between browsers and servers has real-world security implications. Consider a site at https://example.com that relies on the integrity of __Host-
cookies to, e.g., implement CSRF protections:
- Assume that
https://example.com
sets the cookie__Host-xsrf=foo
- An attacker controlling
http://sub.example.com
(either directly or via an XSS) sets a cookie in the victim's browser viadocument.cookie = '__Host-xsrf; Domain=example.com; Path=/folder'
- Subsequent requests to
https://example.com/folder
will contain the HTTP request headerCookie: __Host-xsrf; __Host-xsrf=foo
- The PHP backend will populate the
$_COOKIE
associative array asArray([__Host-xsrf] => )
, effectively ignoring the legitimate cookie in favor of the malicious one.
The attack flow described above defeats double-submit CSRF protections relying on __Host-
cookies for additional security against same-site attackers.
Notice that:
- Safari is not affected by this attack since the restrictions of cookie name prefixes apply
- PHP seems to reject nameless cookies in the form
Cookie: =foo
Proposed solution
Difficult to say since there are discrepancies at multiple levels (specs, browsers, servers). If nameless cookies must be supported for backward compatibility, the specification could mandate the presence of the =
symbol in the name-value-pair produced by the retrieval algorithm. This change should hopefully make the Cookie
header easier to parse, clarifying potential ambiguities at the server-side.