Hijacking OAUTH flows via Cookie Tossing
We recently presented our GitHub Action Research at the Area41 conference in Zurich, Switzerland, where on the first day Thomas Houhou gave an interesting presentation on using Cookie Tossing attacks to increase the impact of Self-XSS issues into something worthy of being reported. This talk was great and showcased some novel uses of how Cookie Tossing can be used to hijack multi-step flows. Cookie Tossing, as a technique, is often overlooked or simply not well-known, and there is little published content on the topic.
We wanted to expand on the limited research currently available and see what additional implications Cookie Tossing attacks can lead to. We’ve found that Cookie Tossing can be used to hijack OAUTH flows and lead to Account Takeovers at the Identity Provider (IdP).
What is Cookie Tossing?
Cookie Tossing is a technique that allows one subdomain (e.g. securitylabs.snyk.io) to set cookies on its parent domain (e.g. snyk.io). Before we look at some problematic scenarios let's first look at what HTTP cookies are.
What are HTTP cookies?
A cookie, defined in RFC 6265, is a small piece of data exchanged between a server and a user's web browser. These cookies are essential for web applications as they enable the storage of limited data and help maintain state information, addressing the inherently stateless nature of the HTTP protocol. Through cookies, user sessions can persist, preferences can be saved, and personalized experiences can be delivered.
Cookies come with various attributes and flags that define their behavior and scope. Here’s a primer on the key cookie attributes and flags:
Attributes | Attributes | Attributes | Attributes | Attributes | Flags | Flags |
Expires | Max-Age | Domain | Path | SameSite | Secure | HttpOnly |
Attributes
Expires:
Sets the expiration date and time of the cookie.
Example:
Expires=Wed, 21 Oct 2024 07:28:00 GMT
Max-Age:
Defines the cookie's lifetime in seconds.
Example:
Max-Age=3600
(1 hour)
Domain:
Specifies the domain the cookie is valid for, allowing subdomains to access it.
Example:
Domain=.snyk.io
Path:
Limits the cookie to a specific path within the domain.
Example:
Path=/account
SameSite:
Controls cookie sending with cross-site requests for CSRF protection.
Values:
Strict
,Lax
,None
Example:
SameSite=Lax
Flags
Secure:
Ensures the cookie is sent over HTTPS only.
Example:
Secure
HttpOnly:
Prevents cookie access via JavaScript, enhancing security.
Example:
HttpOnly
These attributes and flags define the lifespan, scope, and security of cookies, enabling effective and secure user session management.
Setting cookies
Cookies can be set either by the Set-Cookie
header in an HTTP response or by using the JavaScript Cookie API. Here's a basic example of both methods:
Setting cookies with Set-Cookie
header
An HTTP response can include the Set-Cookie
header to set a cookie:
HTTP/1.1 200 OK
Set-Cookie: userId=patch01; Expires=Wed, 21 Oct 2024 07:28:00 GMT; Domain=.snyk.io; Path=/; Secure; HttpOnly; SameSite=Lax
Setting cookies with JavaScript
Using the JavaScript Cookie API, cookies can be set like this:
document.cookie = "userId=patch01; expires=Wed, 21 Oct 2024 07:28:00 GMT; path=/; domain=.snyk.io; secure; samesite=lax";
In the browser, cookies are stored as tuples consisting of the key, value, and attributes. When the browser sends a cookie back to the server, it only includes the key and value, not the attributes. Additionally, browsers have a maximum limit on the number of cookies per domain.
Cookie domains
The Domain attribute of a cookie specifies which domains can access the cookie. By default, a cookie is only accessible to the domain that set it. However, you can use the Domain attribute to extend a cookie's accessibility. For instance, if a cookie is set by blog.snyk.io
with the Domain=.snyk.io
attribute, it will be accessible to all subdomains of snyk.io
, such as app.snyk.io
and snyk.io
itself. Conversely, while the parent domain (snyk.io) can set a cookie to be accessible by all its subdomains using Domain=.snyk.io
, it cannot explicitly set a cookie for a specific subdomain like blog.snyk.io
. This approach allows for enhanced flexibility in multi-subdomain applications while maintaining control over which domains can share cookie data.
Cookie paths and ordering
The Path attribute of a cookie specifies the subset of URLs to which the cookie applies. By default, the cookie is available to the path of the request URL that created it and its subdirectories. For instance, a cookie set with Path=/account
will be accessible to /account and any subdirectories, such as /account/settings. Cookies are also ordered based on their Path attribute; cookies with more specific paths (e.g., /account/settings) are sent before cookies with less specific paths (e.g., /account). This order ensures that the most specific cookie is prioritized when multiple cookies match the request URL.
Exploiting Cookie Tossing
The behavior of both the Domain and Path attributes can be leveraged to perform a Cookie Tossing attack. When an attacker gains control over a subdomain through an XSS vulnerability or by design (such as a service that creates subdomains for customers), they can set cookies on the parent domain. This might not seem dangerous at first, but it can be exploited by setting the attacker's session cookie on the victim's browser for specific endpoints.
For example, the attacker can set a cookie with Domain=.snyk.io
and Path=/api/payment
. As the victim uses the application, everything appears normal, and they are logged into their correct account. However, when they access certain API endpoints that handle sensitive data, such as adding a payment card, the application uses the attacker's cookie instead. This results in the victim's payment method being added to the attacker's account rather than their own.
Exploiting this scenario is not always straightforward, as applications leveraging anti-CSRF tokens can present a significant challenge. In such cases, the request includes the CSRF token from the victim rather than the attacker, causing the legitimate request to fail the anti-CSRF check and be discarded. Despite this hurdle, many applications are still vulnerable to these types of attacks. This is because many JSON-based API endpoints do not implement anti-CSRF mechanisms, relying instead on the Same Origin Policy (SOP) to prevent Cross-Origin requests with a Content-Type of application/json
unless allowed through appropriate Cross-Origin-Resource-Sharing (CORS) headers. However, relying solely on CORS preflights to prevent CSRF attacks can mean that CSRF tokens are deemed unnecessary, which can leave endpoints exposed to cross-subdomain attacks. Additionally, applications that use custom headers or the Authorization header for managing session state are not susceptible to cookie tossing attacks, as the browser does not automatically submit these headers in the way it automatically submits cookies.
It is also worth mentioning that the SameSite cookie attribute does not provide any protection here due to the relaxed definition of a site within the context of SameSite cookies. Due to cookie tossing attacks needing to be launched from a subdomain, this will already satisfy the SameSite requirement even when set to lax
or strict
.
Revisiting GitPod
GitPod is a popular Cloud Development Environment (CDE) that allows its customers to deploy complete development environments within seconds. We have previously looked at GitPod during our research on WebSockets and from this we already knew GitPod hosts its environments on a subdomain of the gitpod.io
domain and it was possible to execute JavaScript on this subdomain. With this knowledge, we decided to explore what implications the Cookie Tossing attack might have using a real application as our test bed to show a legitimate impact.
With GitPod needing access to its user’s source code repositories to allow engineers to checkout and commit code using their product, one idea was to check how the OAUTH flow from GitPod to providers such as GitHub or BitBucket is handled and if they might be susceptible to this attack. After monitoring the flow when configuring a new Git provider we observed a typical OAUTH flow and decided to try tossing our attacker’s session cookies onto the API endpoints relevant for the OAUTH process.
To test this scenario, we created some JavaScript that we hosted on our CDE instance at redacted.ws–eu114.gitpod.io
to toss the _gitpod_io_jwt2_
cookie to contain the value of our attacker's session cookie value, with the path being set to the following:
/api/authorize
/auth/bitbucket/callback
We won’t go into the details on how we were able to host arbitrary JavaScript using our GitPod workspace in this post as we have already covered it here, but once our JavaScript payload is set, we can send a victim the URL to our workspace which when accessed, will set our Cookies as described above.
The victim will not see anything out of the ordinary, and when they go back to using GitPod their session will appear completely normal. However, if the victim decides to connect their account to a Git provider, GitPod will start the OAUTH flow from the attacker account and when the provider redirects the client back to GitPod with the OAUTH code, it will connect the victim's Git account with our attackers GitPod account.
This allowed us to create Gitpod workspaces from any repositories to which the victim user has access, including being able to push new commits and modify source-code within the victim's repositories.
The sequence diagram below can be used to visualize the complete flow for the above Cookie Tossing attack against GitPod.
This issue was reported to GitPod on June 26th 2024 which was promptly fixed on July 1st 2024 in this PR by leveraging the __Host__ cookie prefix. The vulnerability was issued as CVE-2024-21583.
__Host__ cookie prefix
Fortunately, there exists a simple solution for addressing Cookie Tossing issues. The __Host__
cookie prefix can be used to restrict cookies from being sent to any host other than the one that set the cookie. Additionally, the cookies with the __Host__
prefix cannot modify the domain
or path
attributes, which prevents a malicious subdomain from being able to set cookies on the parent domain or target a specific path.
Final thoughts
Cookie Tossing is a unique and often overlooked vulnerability that affects applications not explicitly using the __Host__
cookie prefix. We have demonstrated how this weakness can be exploited to force sensitive requests to execute under an attacker’s session context, potentially exposing sensitive data. In complex workflows, such as those leveraging the OAuth protocol, this can inadvertently grant an attacker access to resources on third-party services. Our previous research indicates that the use of the __Host__
prefix is rare, leaving many organizations—particularly those hosting applications on subdomains—vulnerable. Whenever state-changing requests meet the conditions described in this post, they can be susceptible to hijacking.
Secure your supply chain with Snyk
87% of our respondents were impacted by supply chain security issues. Keep yours secure with Snyk.