Monday, November 20, 2017

About (Weird) CORS Exploitations

During our daily routine, we face lots of different kinds of web applications, which are built differently from one another. Sometimes the developers have the need to pass information from one subdomain to another, or generally between different domains. This need might be for rendering purposes or for crucial functionality such as passing access tokens and session identifiers to another cooperative application.

As you may know, the browsers do not allow AJAX requests to be sent from one domain (or subdomain) to another for security reasons, the security policy is called Same Origin Policy (SOP).

So in order to allow cross-domain communication, the developers had to use different “hacks” to bypass SOP and pass this allegedly “sensitive information”, until Cross-Origin Resource Sharing (CORS) came in to the picture and changed the whole game.

This article sums up an extreme case of CORS misconfiguration that led me to exploit the application’s vulnerable configuration a little bit differently from what I expected.

Chapter A - The (OLD) problem:
The most common method was the use of JSONP, which will be discussed later in this article, and some JavaScript tricks such as using DOM-based objects to store information in them.

JSONP works with specific server endpoints, and retrieves user-related information from them using the user’s session, and a callback that needs to be handled on the client side. Every callback should be wrapped with a padding function (therefore, the P for JSONP). For example:

Victim application runs on domain-A, and publishes a server endpoint called “getUsers”. Note that the callback “users” also determines the name of the padding function:

The callback would be: users({“userA”:”John”,”userB”:”Smith”})

This scenario is extremely vulnerable, since domain-b.com only has to load this endpoint in his site and expect the victim to visit the site. When this happens, an HTTP GET request is sent to domain-a.com along with the victim’s cookie (similar to classic CSRF), and the callback is then returned to domain-b to handle. For example:

function users(json) {
callbackStealer(json.userA);
 }

More information about JSONP can be found over here: https://www.sitepoint.com/jsonp-examples/

Additionally, JavaScript tricks that were used by developers could also leave the site extremely vulnerable to DOM-based XSS attacks. For example:
domain-a might use window.name object to store information, and redirect the tab to domain-b (window.location = "domain-b"). Eventually domain-b will eval() the information stored in window.name.

How can we exploit it? It’s simple:
Our attacking domain (domain-c) should store the XSS payload in window.name
<script> window.name = “some AJAX-based payload to perform a critical action in the targeted application”;
window.location = “http://domain-b.com”; // Remember? This domain should eval(window.name); by design

And since domain-b is executing the code which is stored in window.name, we were able to totally pwn the application, by using a third-party malicious site of our own.

But today, you won’t see this happens much. So you can just use this method to exploit an XSS you already found.

Chapter B - The (NEW) problem:
Later on, HTML5 technology brought a game changing feature called Cross-site Resource Sharing (CORS). This new policy allows the developers to determine which domains are allowed to communicate with their application’s domain, and therefore no hacks are needed – but education and security awareness are!

Frameworks that have the ability to use CORS, used to have an “out of the box configuration”, we'll see some examples below.

The 1st wave of CORS policy taught the browser how to behave and allow two-way interaction between domains & subdomains. This policy tells the browser (using HTTP headers in the server’s response) if he can or cannot interact with the current domain from a different domain.

Some frameworks work with a default configuration that actually takes the origin of the request and automatically allows it by sending the origin back in the response headers. For example:

Request (note the origin was changed to evil.com):


Response:



So much for CORS huh..?
Note that Allow-Credentials: true allows XMLHTTPRequests (XHRs) to send the victim’s cookie from ANY domain. That’s a JavaScript-based CSRF, which is far more "smarter" than the HTML-based version.

But people learned… And hardened their CORS policy. Plus, modern browsers don't even send the HTTP request before it gets an answer for the pre-flight request which looks like this:




In my extreme case, every server endpoint was hardened with a secured CORS policy and didn’t allow any domain to interact with it… Meaning, it wouldn't show the response to the browser which executed the JS code. example:






BUT (!) the pre-flight request was not secured (See example for such configuration in the above OPTIONS request). This means that the browser will allow us to send a request to the targeted domain but not read the response.

Therefore we cannot acquire additional information such as anti-CSRF tokens on nonce values, but we still have an upgraded CSRF that support different HTTP verbs and headers (depending on the response from the pre-flight request).

TIP: we can indicate whether someone executed our script or not by adding a call to our web listener. For example:

$.ajax({some_action_in_the_targeted_application}); new Image().src = "http://web.listener.com?q=somebody just fell in your trap!";

Simple AJAX for Proof of Concept, and you can possibly delete the admin account of the app, change his accounts settings … whatever.

In conclusion,
Remember to always test every endpoint, including the pre-flight request by changing the origin to an arbitrary domain, and ensure that the site is not vulnerable.

Good Luck !

Rotem Tsadok
Head of Offensive Security & Response Unit