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.
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.
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
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
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.
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.
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.
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.
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
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
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
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.
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
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
Responsible Disclosure Timeline
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
access_token values, resulting in multiple
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
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
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.
Thank you for reading this post! If you have any feedback, feel free to contact me on Twitter: @_lauritz_. 👨💻
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.