Preventing CSRF Attacks with Anti-CSRF Tokens

One of the most well-known vulnerabilities, CSRF, is a client-side attack that can induce unwanted actions within a user’s session, including redirecting to a malicious site or stealing session data. Generating and using anti-CSRF tokens correctly is critical to protecting users.

What is an anti-CSRF token?

The idea behind anti-CSRF tokens (or just CSRF tokens) is quite simple: the user’s browser is given a piece of information (a token) that must then be sent back to confirm that the request is legitimate.

To be effective, the token must be unique and impossible for an attacker to guess. The website must validate this token and process HTTP requests only after successful validation to ensure that only a legitimate user can make requests within their authenticated session.

How does CSRF work?

Typically, the attack involves tricking a user into clicking a malicious link that performs unwanted actions in their active session on the website.

In an application without CSRF protection, the server does not check where the request came from—it only sees that it was made by an authorized user (cookies and other credentials are provided). This allows the attacker to use the user’s session for unwanted actions.

Example of a page vulnerable due to missing anti-CSRF token

For instance, a website at www.example.com lacks CSRF defenses. To publish a message on their profile, a user completes a basic HTML form and hits “Submit,” leaving the action exposed to CSRF.

<form action="/action.php" method="post">  
Subject: <input type="text" name="subject"/><br/>  
Content: <input type="text" name="content"/><br/>  
<input type="submit" value="Submit"/>
</form>

This action causes the web browser to send a POST request to the server with any data entered by the user, which is sent in the subject and content parameters:

POST /post.php HTTP/1.1
Host: example.com
subject=I feel pretty good today&content=I just ate a chocolate chip cookie

If the user is logged in and an attacker knows the syntax of the request, they can trick them into clicking a specially crafted link to the malicious hacker’s website, which will cause a CSRF attack, allowing ads to be posted on that user’s profile. The code hosted on the attacker’s site will cause the user’s browser to internally process and submit an HTML form similar to:

<form action="https://example.com/action.php" method="post">  
<input type="text" name="subject" value="Buy my product!"/>  
<input type="text" name="content" value="To buy my product, visit this site: example.biz!"/>  <input type="submit" value="Submit"/>
</form>
<script>  
document.forms[0].submit();
</script>

If a website is vulnerable to CSRF, the user’s web browser will send a POST request similar to the following:

POST /post.php HTTP/1.1
Host: example.com
subject=Buy my product!&content=To buy the product, follow this link: example.biz!

Adding a simple CSRF token

To protect against the attack, teams can use tokens: the server generates it when the user logs in, passes it to the browser, and all forms in the application must contain a hidden field with this token.

Provided the tokens are properly generated and validated (as in the example below), this should eliminate the CSRF vulnerability:

<form>
  Subject: <input type="text" name="subject"/><br/>
  Content: <input type="text" name="content"/><br/>
  <input type="submit" value="Submit"/>
  <input type="hidden" name="token" value="dGhpc3Nob3VsZGJlcmFuZG9t"/>
</form>

The server accepts and processes POST requests only if they contain the required token in the request parameter, for example:

POST /post.php HTTP/1.1 Host: example.com subject=I'm feeling pretty good today&content=I just ate a chocolate chip cookie&token=dGhpc3Nob3VsZGJlcmFuZG9t

How to securely generate and validate anti-CSRF tokens

There are many different ways to generate and validate anti-CSRF tokens depending on the website. First of all, if the platform or programming language already includes CSRF prevention functionality, it is usually better to rely on it or find a reliable external library rather than trying to implement a custom one.

But there are also a number of best practices:

  • Tokens should be cryptographically strong (sufficiently randomized) and at least 128 bits long to resist brute-force attacks.
  • Avoiding reusing tokens—making them session-specific, regenerating them after important actions, and setting an expiration date.
  • The server should validate tokens every time using a secure comparison method (e.g., by checking hashes).
  • Never transmitting tokens over unencrypted HTTP traffic, and never including them in GET requests so that they don’t end up in the URL.
  • Passing anti-CSRF tokens in the SameSite cookie. Additionally, using the HTTPOnly attribute can prevent access to the cookie via JavaScript.
  • Ensuring there is no XSS (cross-site scripting) vulnerability, as an attacker can use this flaw to bypass CSRF protection.

Using additional layers of CSRF protection

A separate token for each form

To balance security and convenience, a separate token can be generated for each form. To do that, teams should hash the token along with the form file name, for example:

hash_hmac('sha256', 'post.php', $_SESSION['internal_token'])

To verify, it is needed to compare the two hashes. If the token is valid and the same form was used, they will match.

A separate token for each request

A separate token can be used for each request by simply invalidating each one after it is checked. However, this method has a number of disadvantages that should be considered:

  • New tokens need to constantly be generated, which can be a heavy load on the server.
  • The user will not be able to use multiple tabs at the same time.
  • The “back” button in the browser cannot be used—teams will have to implement internal navigation in the application.

Stateless CSRF protection

Typically, each token is stored on the server for validation, which allows the server to remember the session state. In some cases, for example, if the web page or application is overloaded/or the server storage is limited, teams can use stateless CSRF protection to avoid having to store tokens on the server side.

The easiest way to do this is to use a double-submit cookie pattern, either signed or unsigned.

This works by the server setting a random value in a cookie before the user authenticates. The server then expects this value to be sent with each request, for example, using a hidden form field.

The pattern relies only on a random value that an attacker cannot guess, while the signed pattern further encrypts this value with a secret key on the server side.

Some implementations also use timestamps as part of the token generation and validation process.

CSRF protection in asynchronous (Ajax) requests

Many modern applications use Ajax instead of traditional HTML forms. In this case, implementing standard anti-CSRF tokens can be difficult.

An alternative approach is to use a custom header for client requests. If used, the backend will reject any requests without this HTTP header.

But it is worth noting that a too weak CORS (cross-origin resource sharing) configuration can allow an attacker to set cookies as well as custom headers, so you should be careful to limit this to sources that the teams have strict control over.

As an example of implementing CSRF protection by default, the JavaScript prototype method XMLHttpRequest.open() can be overridden to one that automatically sets its own anti-CSRF header. Similar mechanisms are available in popular libraries and frameworks.

Some older online resources advise against using anti-CSRF tokens for APIs because they are unnecessary, but since many websites are now completely API-centric, this creates risks. As with Ajax requests, custom request headers are a good way to implement CSRF protection for APIs.

Anti-CSRF tokens for login forms

A common misconception is that anti-CSRF tokens are only needed when the user is logged in, so CSRF protection for login forms is unnecessary.

While it is not possible to impersonate a user directly before logging in, the lack of CSRF protection in login forms can allow attacks that reveal sensitive information after tricking the user into logging in as an attacker.

This can be done in the following ways:

Note: This information is provided solely for the purpose of risk awareness and understanding the specifics of attacks by cybersecurity professionals, and is not intended to encourage malicious use.

  1. The attacker registers an account.
  2. Then they trick the user into logging in to the website using the attacker’s credentials. This may require only a little social engineering.
  3. The target uses the website without realizing that they are logged in as the malicious hacker.
  4. The attacker can track the target’s data, including activity, personal information, or stored financial data, potentially performing actions on their behalf (e.g., making purchases using stored credit card details).

CSRF protection also means XSS protection

Even if anti-CSRF tokens are configured correctly, the presence of an XSS vulnerability (or other flaws) in a web application can bypass these protections.

For example, an attacker could use cross-site scripting (XSS) to run a script that silently obtains a new version of the form along with the current (valid) token, allowing it to perform CSRF for that user session.

Vulnerability detection in web applications, including CSRF and XSS

Regardless of whether anti-CSRF tokens are implemented, it is a best practice to test the security of a website to find its vulnerabilities, including CSRF and XSS.

What to use:

  • Dynamic Application Security Testing (DAST, for example, Invicti, based on Netsparker and Acunetix): does not require source code and allows teams to test the website at runtime, providing the most realistic picture of the current security posture.
  • Static Application Security Testing (SAST, e.g. Mend.io): is used during web application development, which allows checking the security state from the early stages of the project.

A combination of these methods is the best practice, as SAST cannot detect vulnerabilities at runtime, and DAST cannot scan the entire code base if needed and cannot be implemented at very early stages of development.

You can test Invicti (DAST) solution with automatic vulnerability confirmation technology for free, as well as Mend.io (SAST, SCA, Container Security), which is distinguished by the speed and power of the scanning engine.

To do this, please leave your contact details in the form below:

Request for free Invicti/Mend.io Trial



    Subscribe to news