How to prevent CSRF attacks by using anti-CSRF tokens

What is an anti CSRF token?

The idea behind anti-CSRF tokens (also called just CSRF tokens) is simple: to give the user’s browser a piece of information (a token) that it then has to send back to prove a request is legitimate. To be effective, the token must be unique and impossible to guess for a third party. The application must also verify the token and only process HTTP requests after successful verification to ensure that only the legitimate user can send requests within their authenticated session.

What is cross-site request forgery (CSRF)?

Cross-site request forgery is a web vulnerability that allows an attacker to send requests impersonating a legitimate user’s browser activity to perform state-changing actions on a server. A typical CSRF attack involves tricking a user into opening a malicious link that then attempts to execute unintended actions within a user’s active application session. The impact of CSRF depends on the application being targeted.

Learn more about cross-site request forgery.

As so often in security, there are many ways to implement anti-CSRF tokens and many details to consider along the way, but let’s start with a very basic example to illustrate the concept.

Example of a vulnerable page without a CSRF token

Say you run some web application on www.example.com without any CSRF protection. To publish a message on their profile in the app, a user completes a simple HTML form and clicks the Submit button:

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

The submit action causes the web browser to send a POST request to the server with whatever data the user entered being sent in the subject and content parameters:

POST /post.php HTTP/1.1
Host: example.com

subject=I am feeling pretty good today&content=I just ate a cookie, chocolate chip

If the user is logged in and the attacker knows the request syntax, getting the user to click a specially crafted link to the attacker’s site could enable a CSRF attack to publish an ad on that user’s profile. The code hosted on the attacker’s site would 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 the site 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 my product, visit this site: example.biz!

On an unprotected page, this could achieve CSRF and post the fake message on the user’s profile at example.com. This is because the server treats the forged request as coming from an authenticated user and doesn’t care or check what site it originated from. As long as the action was initiated by the user (to include their session cookie), it doesn’t matter where the code is hosted—which is why it’s called cross-site request forgery.

Example of adding a simple CSRF token

To prevent such attacks, you decide to protect your site using simple token-based CSRF mitigation. Your web server sets the token and sends it to the browser right after the user logs in, and all form submissions in your app include a hidden field containing that unique token. Assuming proper token generation and validation (see 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 should then only accept POST requests from the user if they include this exact value of the token request parameter, for example:

POST /post.php HTTP/1.1
Host: example.com

subject=I am feeling pretty good today&content=I just ate a cookie, chocolate chip&token=dGhpc3Nob3VsZGJlcmFuZG9t

With this protection in place, an attacker who tries to perform CSRF via a malicious site cannot fake HTTP requests because they don’t know the current token set in the valid user’s cookie. And because your server now rejects all requests without this token, any CSRF attack attempts will fail.

How to securely generate and verify CSRF tokens

There are many different ways of generating and checking anti-CSRF tokens depending on your application requirements. First and foremost, if your application framework or programming language already includes CSRF prevention functionality, it is usually best to rely on this or find a reputable external library rather than trying to implement your own. Whichever synchronizer token pattern you choose, make sure your tokens and their use meet several basic requirements:

  • Tokens should be cryptographically secure (sufficiently random) and have a minimum length of 128 bits to resist brute-force attacks.
  • Prevent token reuse by making tokens session-specific, regenerating them after security-sensitive actions, and expiring them after a time that makes sense for security and usability.
  • Make sure the server really validates tokens every time and uses a secure comparison method (e.g. comparing cryptographic hashes).
  • Never send CSRF tokens in unencrypted HTTP traffic, and never send them in GET requests to prevent revealing tokens in URLs (like in server logs or in a Referer header alongside other referrer information).
  • Transmit CSRF tokens in SameSite cookies to further mitigate cross-site attacks. In addition, using the HTTPOnly attribute can prevent JavaScript access to cookies.
  • Ensure you don’t have any cross-site scripting (XSS) vulnerabilities, as those could allow attackers to bypass anti-CSRF techniques.

Using different levels of CSRF protection

With a basic anti-CSRF token similar to the one shown above, you set the token in the user session cookie upon login and verify that same token for every form during the active session. In many cases, this level of CSRF protection could be enough, especially if you have session time-out limits, but some use cases may need a more secure approach.

Separate CSRF protection for each form

To balance security and usability, you can generate a separate token for each form you use.

To do this, generate a token but do not expose it directly to the user’s browser. Instead, hash the token combined with the filename of the form, for example:

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

To verify, compare the two hashes generated in this way. If the token is valid and the same form was used, the hashes will match.

Separate CSRF protection for each request

When a very high level of protection is required, perhaps in a banking application, you can use a separate token for each request simply by invalidating every token after it is verified. Implementing per-request tokens has several usability drawbacks that you should carefully consider:

  • Each request requires the server to generate a new random token, so server performance and resource limits could be a factor.
  • You can’t use the same app in multiple tabs.
  • You can’t use the browser’s back button for navigation, only the app’s own internal navigation features.

Using non-persisted CSRF tokens

Normally, each token is stored on the server for verification, allowing the server to remember the session state. In some cases, like if your web page or application is very busy and/or you have limited server storage, you may want to use stateless CSRF protection to eliminate the need to store tokens on the server side. The easiest way to do this is using the double-submit cookie pattern, either signed or unsigned (what the OWASP CSRF Prevention Cheat Sheet calls the “naive” double-submit cookie).

The idea behind double-submit cookies is that the server sets a random value in a cookie before the user even authenticates. The server then expects this value to be sent with every request, for example by using a hidden form field. The “naive” pattern relies just on having a random value that is unguessable to an attacker, while the signed pattern additionally encrypts this value using a server-side secret key. Some implementations also use timestamps as part of the token generation and verification process.

CSRF protection for asynchronous (Ajax) requests

Many modern apps rely on Ajax requests instead of traditional form tags and submissions, which can make implementing typical anti-CSRF tokens tricky. An alternative method is to use a custom request header appended by the client to requests that need CSRF protection. The header can be any key-pair value that doesn’t conflict with existing headers. Where this custom header is used, the backend will reject any requests without that HTTP header. Note that an overly lax CORS (cross-origin resource sharing) setting may allow an attacker to set cookies as well as custom headers, so be careful to restrict this to origins that you definitely control.

As an example of applying CSRF protection by default, you can override the prototypical XMLHttpRequest.open() JavaScript method with one that automatically sets a custom anti-CSRF header. Similar mechanisms are available in popular libraries and frameworks.

Some older online resources advise against using anti-CSRF tokens for API endpoints as unnecessary—but with so many websites and applications being entirely API-driven, this no longer holds true. As with Ajax requests, custom request headers are a good way to implement CSRF protection for APIs.

Anti-CSRF tokens for login forms

It’s a common misconception that anti-CSRF tokens are only needed when a user is logged in, so you don’t need CSRF protection for login forms. While it’s true you can’t directly impersonate a user before login, omitting CSRF protection for login forms may allow attacks that expose sensitive information after tricking the user into logging in as the attacker. An attack could be performed as follows:

  1. An attacker creates an account in your web application.
  2. The attacker tricks a victim into logging into your app using the attacker’s credentials. Without CSRF protection for the login form, this may only need a little social engineering.
  3. The victim uses your application, unaware they are logged in as the attacker.
  4. The attacker may track the victim’s data in the application, including activity, personal information, or stored financial data, potentially performing actions on behalf of the victim (like making purchases using stored credit card data).

To minimize the risk of such attacks, it’s best to use some type of CSRF protection on login pages as well.

CSRF prevention also means XSS prevention

While anti-CSRF tokens can—when implemented correctly—provide solid protection against CSRF attacks, having other vulnerabilities in your application may allow attackers to bypass some CSRF protection measures. Most importantly, if your web application has a cross-site scripting (XSS) vulnerability, an attacker may be able to use XSS to run a script that silently fetches a new version of a form complete with a current (valid) CSRF token, allowing them to perform CSRF for that user session. 

To prevent this and maintain a good web application security posture overall, make sure you regularly scan your websites, applications, and APIs for all types of vulnerabilities, not just CSRF.


Frequently asked questions about anti-CSRF tokens

How does an anti-CSRF token protect from CSRF attacks?

An anti-CSRF token is a security measure that prevents cross-site request forgery attacks by requiring a unique token with each request to ensure that requests originate from legitimate users and not from malicious sources.
 
Learn more about CSRF attacks and the dangers they can bring.

What are the benefits of implementing anti-CSRF tokens for website protection?

Using CSRF protection, whether in the form of anti-CSRF tokens or another method, helps protect website and application users against unauthorized actions initiated by malicious actors, thus protecting sensitive user data and maintaining the integrity of web applications.
 
Read about using SameSite cookies to mitigate CSRF attacks.

Are anti-CSRF tokens the only way of preventing cross-site request forgery?

No, there are several other methods, including custom request headers and SameSite cookies. Anti-CSRF tokens themselves also come in various shapes and sizes, from a simple random value being included in a hidden form field on every form after login to more sophisticated tokens that are sent even before user authentication.
 
Read more about cross-site request forgery and ways to protect your site against attacks.

The post How to prevent CSRF attacks by using anti-CSRF tokens appeared first on Invicti.

Post a Comment

0 Comments