Basically CORS lets us define a set of ‘rules’ to specify which resources can access responses from our server. By default no rule is defined, so SOP will only allow interactions form the same domain.
What’s the point of SOP and CORS?
SOP is a huge security mechanism that controls how a resource (like a webpage or scripts) interact with other resources. SOP prevents code from an iframe (loaded with some website’s code) from accessing your webpage data, for example. It’s an isolation mechanism.
CORS on the other hand was introduced to help bypassing this restrictions, as sometimes you may want these resources to comunicate between them.
CORS Response Headers
As CORS works through some HTTP headers it’s important to understand which the most common headers used. Lets take a look at some:
This is the most important header as it specifies which origins can access the server resources:
If a server sends the header above, it will instruct the browser that it accepts Cross Origin requests from https://thesecurityvault.com
A wild card can be set, although not recommended
Like the header above, this one instructs which method can be invoked
Access-Control-Allow-Methods: POST, GET, OPTIONS
In this case we are allowing other webpages to do POST, GET and OPTION requests to our server
Specifies which headers can be sent.
CORS Preflight request
The preflight request is a request automatically sent by the browser to check if the server knows about CORS and if it allows the request.
This is where things start to get tricky, because not all requests trigger a preflight request.
To not trigger a preflight request this is what’s needed:
- Method: The HTTP method needs to be a GET, POST or HEAD
- Headers: Besides the default headers automatically injected, only CORS safe headers can be set.
- Content Type: Needs to be ‘application/x-www-form-urlencoded’, ‘text/plain’ or ‘multipart/form-data’.
- ReadableStram: Do not open a ReadableStram in the request
- No Listeners on XMLHttpRequestUpload
Mozilla has an awesome documentation about this as well.
Lets see some examples… I like to use https://webhook.site to test requests. It’s really nice and powerful.
Lets start by something that we know will trigger a preflight request:
We have an error in the browser, but lets see in webhook:
We have an OPTIONS request, but no patch… so the method was blocked by CORS…
Now lets try with a request that will not trigger a preflight request:
As you can see, we had a CORS error, as the page at ‘https://webhook.site/17ac55b4-c06e-4a7a-9c5a-314c60b57b87' is not allowing CORS requests.
But lets see what heppens, at the webhook side:
As you can see webhook successfully received a GET request. No preflight, which according to the rules above makes sense. We sent an allowed method, an allowed content type, no extra headers…
Now if we try for example to set a POST with a body like:
We still have the error in the browser, but the request keeps being made.
So what happens when no preflight is made is that the browser will only know how to comply with CORS policy when receives a response with the relevant headers, so the request is made anyway, but if a CORS policy is set it will only block the response…
SOP really helps out preventing some attacking techniques, like CSRF, but with the right conditions, as seen above it can be bypassed.
HTML’s Form tag
The preflight rules above may seem a little bit weird… Why doesn’t a regular post request trigger a preflight request? Looks like a poor design…
In fact this has its logic underneath… and part of it is the tag. If you look carefully for the rules to trigger the preflight you will notice that a form tag will never trigger a preflight, and it shouldn’t, otherwise it would just brake a lot of websites.
So this may by a good attack vector for what we’ve seen above. Older websites are still heavily depending on forms, so most of those requests are being sent with ‘application/x-www-form-urlencoded’ which we already know that can be bypassed with fetch, or even with other form tags
Basically a webpage would call a script like:
and notice the callback query param, that would be used by the server, to wrap the entire response data:
This code would be injected into the webpage, calling the processData method.
This still works, but shouldn’t be used, specially because JSONP can be a security risk, causing XSS or Reflected File Download
The easiest and faster way to bypass SOP when doing requests is by using a CORS proxy. This can basically inject the headers (seen above) in the responses allowing all types of requests from all origins.
There are some nice online CORS proxy tools like https://corsproxy.github.io/
Using this services may raise some security concerns like is the proxy sniffing the data you send/receive from it?
Best approach to go with a CORS proxy would be to use an open source tool, and deploy it yourself.
You can also use SharpCorsProxy which is an OpenSource project I did some time ago.
Some times, developers tend to add ‘null’ as an allowed origin in CORS policy. This is to help on local developments as local pages usually have ‘null’ as origin. This is sometimes forgotten, and you can even see this config in production environments.
Getting a null origin is fairly easy, you can get it by having a local page, or just by doing a request inside an iframe.