Back in June my team and I found a vulnerability in the way multiple frameworks/languages parse cookies which could allow a potential attacker to bypass cookie prefixes. At its core the vulnerability exploits the fact that these languages decode the entire cookie string, which includes the name of the cookie. In most cases that’s fine however in some unique cases certain assumptions are made around the naming of cookies which this exploit is able to bypass. Rails (rack), Dotnet and PHP were all affected.

In each of the affected languages the flaw allowed for a __%48ost- or __%53ecure- cookie to be set without meeting the required attributes (I.e. set without HTTPS, from root domain, or from a secure page). This means a malicious cookie set by an attacker could potentially craft a malicious __%48ost- and set it on their victim. Note: another exploit such as XSS would be required to actually set the cookie. What makes this dangerous is that an XSS vulnerability on a subdomain could even be used, bypassing any assumptions the server has around the cookie, for example __Host- cookies only being set on the parent domain while __%48ost cookies can be set anywhere.

If an attacker had XSS on a subdomain they could use the following snippet to set a malicious __%48ost- cookie that would be read by the parent domain as a __Host- cookie. Then the vulnerable language would decode the cookie and treat it as a __Host- prefixed cookie:

1
document.cookie = "__%48ost-evil=evil; domain=.example.com";

This is the example test case I submitted to rails to catch this issue. Similar tests were also submitted to PHP and Dotnet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
describe Rack::Utils, "malicious cookie" do
# Fails and __Host-evil reads the malicious value and sets it as the cookie
# rather than reading the actual __Host cookie
#
# Furthermore, browsers enforce HostOnly for `__Host-` cookies but they would
# not enforce it for "__%48ost" cookies so a malicious script could potentially
# set this cookie knowing it would be parsed as the `__Host-` cookie
#
# Lastly, when the cookie is made it could be set with the `.example.com` domain
# wildcard, thus a malicious script on a subdomain could set the cookie and it
# would be parsed by the root domain
#
# This is due to the cookie being unescaped, thus:
# URI.unescape("__%48ost-evil") => "__Host-evil"
#
# Currently fails, should be passing
it "doesnt parse malicious __Host cookie" do
env = Rack::MockRequest.env_for("", "HTTP_COOKIE" => "__%48ost-evil=evil;__Host-evil=abc")
cookies = Rack::Utils.parse_cookies(env)
cookies.must_equal({ "__%48ost-evil" => "evil", "__Host-evil" => "abc" })
end
end

Ultimately this vulnerability lead to 3 CVEs:

Thanks to my team at GitHub for helping me identify this issue! Thanks to the Rails, PHP and Microsoft dotnet teams for the fixes!