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:

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:

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.

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.

Comsec Group Blog

Comsec Group, founded in 1987, is a pioneering market leader, providing all-inclusive Cyber and Information Security services to clients around the globe. Our mission is to serve our clients as trusted advisors, by securing their information and operational assets, ensuring the achievement of their business goals.