POSTS
Insufficient Redirect URI validation: The risk of allowing to dynamically add arbitrary query parameters and fragments to the redirect_uri
In this post, I will discuss an OAuth 2.0 and OpenID Connect 1.0 implementation flaw pattern that was or is present even in well-known implementations from Github, Stackoverflow and Microsoft.
It has been observed in the wild that some Identity Provider (Authorization Server) implementations allow to dynamically add query parameters to the Authorization Request and forward these parameters within the Authorization Response. As a result, depending on the Relying Party (Client) implementation, this pattern may enable HTTP parameter pollution attacks.
Background
The OAuth 2.0 RFC outlines how the redirect_uri
parameter should be handled if present within the Authorization Request:
When a redirection URI is included in an authorization request, the authorization server MUST compare and match the value received against at least one of the registered redirection URIs (or URI components) as defined in [RFC3986] Section 6, if any redirection URIs were registered. If the client registration included the full redirection URI, the authorization server MUST compare the two URIs using simple string comparison as defined in [RFC3986] Section 6.2.1.
https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2.3
The redirect_uri
is allowed to include query parameters:
The redirection endpoint URI MUST be an absolute URI as defined by [RFC3986] Section 4.3. The endpoint URI MAY include an “application/x-www-form-urlencoded” formatted (per Appendix B) query component ([RFC3986] Section 3.4), which MUST be retained when adding additional query parameters. The endpoint URI MUST NOT include a fragment component.
Notably, these query parameters need to be pre-registered, so that the Authorization Server can apply the required simple string comparison to the entire provided redirect_uri
.
Furthermore, apparently there is no clear definition how an implementation is supposed to handle multiple occurrences of the same query parameter. The original RFC3986 does not comment on this at all. Thus, different implementations and libraries handle this incident quite different, see this talk (slide 9).
Recently Youssef Sammouda (@samm0uda) published a blog post with multiple bugs in Facebook’s OAuth implementation. The root cause of the very first vulnerability was also a parameter pollution issue.
Implementation Inconsistencies
Even though the specification is quite strict regarding the redirect_uri
validation, even popular implementations handle this crucial verification step lax.
A relatively harmless behaviour that was observed in the wild is to allow slight differences between the registered and provided redirect URI value but to stick to the registered value. For instance, Facebook was observed to accept additional query parameters on Authorization Request, but for the Authorization Response only the registered redirect_uri
was used.
In contrast, Github as an example actually accepts not-registered query parameters and uses these parameters within the Authorization Response.
Impact
At worst, the aforementioned behaviour could enable a malicious actor to inject a controlled code
into an OAuth 2.0 flow that is performed in a user’s browser and session. As a result, the end-user would be authenticated within the attacker-controlled account (see Login CSRF).
Apparently there is no up-to-date research on how implementations handle multiple occurrences of the same parameter. To my knowledge, the most recent research that was published dates back to 2009 (OWASP EU09 Poland: HTTP Parameter Pollution), slide 9). Notably, according to this resource, some Java-based frameworks use the very first occurring parameter.
As an in-the-wild and up-to-date example, Keycloak can be configured as Service Provider and is written in Java. Some Java libraries handle query parameters with a “first come first serve” approach. If Keycloak encounters multiple code
values within the Authorization Response, only the very first occurrence is processed.
Limitations
If a state
or PKCE
is used, the aforementioned flaw most likely can not be directly exploited, as to craft a malicious Authorization Request, a malicious actor would already be in possession of sensitive OAuth 2.0 parameters that would enable them to directly spoof the Authorization Response, for instance a CSRF attack against a user.
Example: Github
The following steps require a close look to the actual traffic and requests that are sent between the participating parties. Therefore, I recommend to use an intercepting proxy to observe the actual traffic. But at first we will use the shortcut without thoroughly analysing what is going on:
- Click the following link or open it in any browser: https://github.com/login/oauth/authorize?client_id=01b478c0264a1fbd7183&scope=user:email&redirect_uri=https%3a%2f%2fstackauth.com%2fauth%2foauth2%2fgithub%3fcode%3dlauritz&state=aaaaaa&response_type=code
- Login at Github.
- Observe that you are redirected to https://stackauth.com/auth/oauth2/github?code=lauritz&code=[REDACTED]&state=aaaaaa
Now let’s have a closer look at the outcomes of the login request: The value of the first code
parameter in this request is lauritz
, the latter code
parameter holds the benign value issued from Github. The code
parameter is crucial in OAuth 2.0 and would be used by the application in the following to retrieve a valid access_token
.
But how could this happen?
Github does not sufficiently validate the redirect_uri
parameter within the Authorization Request. The requests that are performed are as follows:
- With clicking the link, we launch a login flow (Authorization Request):
GET /login/oauth/authorize?client_id=01b478c0264a1fbd7183&scope=user:email&redirect_uri=https%3a%2f%2fstackauth.com%2fauth%2foauth2%2fgithub%3fcode%3dlauritz&state=aaaaaa&response_type=code HTTP/2
Host: github.com
[...]
After authentication, Github responds with the Authorization Response whose destination is stackauth.com:
GET /auth/oauth2/github?code=lauritz&code=aaaaaaaaaaaaaaaaa&state=aaaaaa HTTP/1.1
Host: stackauth.com
As you can see, Github embeds both, the real code value and the injected attacker-controlled “fake” code. The following steps then may be implementation specific to the Service Provider.
Responsible Disclosure Timeline
- 2021-08-12: Report via https://hackerone.com/github
- 2021-08-12: Report is closed as informative.
- 2021-08-13: Github gives permissions to disclose post.
Example: Stackoverflow
The Stackoverflow OAuth 2.0 implementation (https://api.stackexchange.com/docs/authentication) implements a Single-Sign On flow using OAuth 2.0. An insufficient validation of the redirect_uri
parameter within the Authorization Request allows to inject arbitrary parameters into the Authorization Response. Strikingly, the implementation does only include each parameter once, allowing a malicious actor to overwrite response parameters.
Steps to reproduce
The following application (client_id = 20816) with “OAuth Domain” security.lauritz-holtmann.de
was registered at Stackoverflow.
An exemplary Authorization Request looks as follows: https://stackoverflow.com/oauth?client_id=20816&redirect_uri=https%3a%2f%2fsecurity.lauritz-holtmann.de%2f%3fcode%3dlauritz&response_type=code&state=a
The application responds as follows:
HTTP/2 302 Found
Cache-Control: private
Location: https://security.lauritz-holtmann.de/?code=lauritz&state=a
[...]
As one can see, the code
parameter reflects the value that was previously injected with the Authorization Request (redirect_uri
). Depending on the Service Provider implementation, this code
would be directly used by the configured Service Provider.
In contrast, for a benign request (https://stackoverflow.com/oauth?client_id=20816&redirect_uri=https%3a%2f%2fsecurity.lauritz-holtmann.de%2f&response_type=code&state=a), the response looks as follows:
HTTP/2 302 Found
Cache-Control: private
Location: https://security.lauritz-holtmann.de/?code=r2iRH[...]&state=a
[...]
Responsible Disclosure Timeline
- 2021-08-23: Initial report via https://stackexchange.com/about/security.
- 2021-09-09: Stackoverflow notifies about fix and agrees to disclose this post.
Example: Microsoft
Microsoft’s login.live.com as OAuth 2.0 Authorization Server allows to append arbitrary query parameters (?
) and fragments (#
) to registered redirect_uri
values. This enables the injection of malicious code
or access_token
values, resulting in multiple code
or access_token
parameters being forwarded by the MS service.
An exemplary Authorization Request is given in the following:
GET /oauth20_authorize.srf?client_id=0000000000047086&response_type=token&redirect_uri=https%3a%2f%2fwww.office.com%2fhtml%2fMsaToken.html%23access_token%3dtest&response_mode=fragment&scope=onedrive_implicit.access&state=iframe%7Caaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaa&prompt=none HTTP/1.1
Host: login.live.com
[...]
After authentication, the Authorization Server responds with the Authorization Response whose destination is www.office.com:
HTTP/1.1 302 Found
Content-Type: text/html; charset=utf-8
Location: https://www.office.com/html/MsaToken.html#access_token=test#access_token=[REDACTED]&token_type=bearer&expires_in=3600&scope=onedrive_implicit.access&state=iframe%7aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaa
[...]
As the above HTTP response illustrates, this Authorization Response includes multiple access_token
values within the fragment of the URL. The very first access_token
was injected within the Authorization Request and then reflected within the Authorization Response.
Responsible Disclosure Timeline
- 2021-11-03: Initial report via Microsoft Security Response Center (MSRC, https://msrc.microsoft.com/).
- 2021-11-03: MSRC informs that they do not consider this a vulnerability.
References
- OAuth 2.0 RFC
- OWASP: Login CSRF
- OWASP EU09 Poland: HTTP Parameter Pollution, Luca Carettoni and Stefano di Paola
- YOUSSEF SAMMOUDA: More secure Facebook Canvas : Tale of $126k worth of bugs that lead to Facebook Account Takeovers
Thank you for reading this post! If you have any feedback, feel free to reach out via Mastodon, Twitter or LinkedIn. 👨💻
You can directly tweet about this post using this link. 🤓
If you are interested in Single Sign-On Security, the following series of blog posts might be interesting for you:
- [Overview] Common Issue Patterns and Derived Security Considerations
- [Implementation] Login Confusion
- [Implementation] Injection of CRLF sequences
- [Implementation] SSRF issues in real-life OIDC implementations
- [Specification] Redirect URI Schemes
- [Specification] Reusable State parameter
- [Responsible Disclosure] Lessons learned during Responsible Disclosure of OIDC/OAuth related issues
Special thanks to the security teams of Github and Stackoverflow for the permission to disclose this blog post.