Protecting Your Users Against CSRF

Cross-site request forgery (CSRF) vulnerabilities can be used to trick a user’s browser into performing an unwanted action on your site.

Risks

Prevalence Common
Exploitability Easy
Impact Harmful

Any function that your users can perform deliberately is something they can be tricked into performing inadvertently using CSRF. As we saw in our example, in the most malign cases, CSRF attacks can spread themselves as a worm.

CSRF attacks in the past have been used to:

It is hard to estimate the prevalence of CSRF attacks; often the only evidence is the malicious effects caused by the attack. CSRF is routinely described as one of the top-ten security vulnerabilities by OWASP.

Protection

Websites consist of a combination of client-side and server-side code. The client-side code is HTML and JavaScript that is rendered by and executed in the browser. This allows users to navigate to other URLs on your site, submit HTML forms to the server, and trigger AJAX requests via JavaScript. Your server-side code will intercept the data sent in the HTTP request, and act upon it appropriately.

These server-side actions can also be triggered by forged HTTP requests, unless you explicitly put in protective measures. A CSRF attack occurs when a malicious actor tricks a victim into clicking on a link, or running some code, that triggers a forged request. (This malicious code is typically hosted on a website owned by the attacker, on another domain – hence the “cross-domain” denomination.)

Protecting against CSRF (commonly pronounced “sea-surf”) requires two things: ensuring that GET requests are side-effect free, and ensuring that non-GET requests can only be originated from your client-side code.

REST

Representation State Transfer (REST) is a series of design principles that assign certain types of action (view, create, delete, update) to different HTTP verbs. Following REST-ful designs will keep your code clean and help your site scale. Moreover, REST insists that GET requests are used only to view resources. Keeping your GET requests side-effect free will limit the harm that can be done by maliciously crafted URLs–an attacker will have to work much harder to generate harmful POST requests.

Anti-Forgery Tokens

Even when edit actions are restricted to non-GET requests, you are not entirely protected. POST requests can still be sent to your site from scripts and pages hosted on other domains. In order to ensure that you only handle valid HTTP requests you need to include a secret and unique token with each HTTP response, and have the server verify that token when it is passed back in subsequent requests that use the POST method (or any other method except GET, in fact.)

This is called an anti-forgery token. Each time your server renders a page that performs sensitive actions, it should write out an anti-forgery token in a hidden HTML form field. This token must be included with form submissions, or AJAX calls. The server should validate the token when it is returned in subsequent requests, and reject any calls with missing or invalid tokens.

Anti-forgery tokens are typically (strongly) random numbers that are stored in a cookie or on the server as they are written out to the hidden field. The server will compare the token attached to the inbound request with the value stored in the cookie. If the values are identical, the server will accept the valid HTTP request.

Most modern frameworks include functions to make adding anti-forgery tokens fairly straightforward. See the code samples below.

The Google Chrome team added a new attribute to the Set-Cookie header to help prevent CSRF, and it quickly became supported by the other browser vendors. The Same-Site cookie attribute allows developers to instruct browsers to control whether cookies are sent along with the request initiated by third-party domains.

Setting a Same-Site attribute to a cookie is quite simple:

Set-Cookie: CookieName=CookieValue; SameSite=Lax;
Set-Cookie: CookieName=CookieValue; SameSite=Strict;

A value of Strict will mean than any request initiated by a third-party domain to your domain will have any cookies stripped by the browser. This is the most secure setting, since it prevents malicious sites attempting to perform harmful actions under a user’s session.

A value of Lax permits GET request from a third-party domain to your domain to have cookies attached - but only GET requests. With this setting a user will not have to sign in again to your site if the follow a link from another site (say, Google search results). This makes for a friendlier user-experience - but make sure your GET requests are side-effect free!

Include Addition Authentication for Sensitive Actions

Many sites require a secondary authentication step, or require re-confirmation of login details when the user performs a sensitive action. (Think of a typical password reset page – usually the user will have to specify their old password before setting a new password.) Not only does this protect users who may accidentally leave themselves logged in on publicly accessible computers, but it also greatly reduces the possibility of CSRF attacks.

Code Samples

Django

To enable CSRF protection in Django, configure your middleware appropriately. Then add anti-forgery tokens to your HTML forms in the following manner:


<form action="." method="post">
  {% csrf_token %}
</form>  

Tokens can be checked using a pre-processor, or manually.

Flask

The flask-wtf makes is eady to add anti-forgery tokens to your HTML forms. Simply wrap your Flask app in a CSRFProtect object:


from flask import Flask
from flask_wtf.csrf import CSRFProtect

csrf = CSRFProtect()

def create_app():
    app = Flask(__name__)
    csrf.init_app(app)
    

Then you can add anti-forgery tokens to HTML forms:


<form method="post">
    {{ form.csrf_token }}
</form>
   

…or incorporate them in AJAX calls:


var csrf_token = "{{ csrf_token() }}";

$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrf_token);
        }
    }
});

Rails

Rails includes CSRF counter-measures out-of-the-box. Protecting against CSRF is usually as simple as adding the following snippet in your Rails controller:


protect_from_forgery with: :exception

Rails will handle the rest of the logic – CSRF tokens will be included in any HTML form, and validated when they are received back in non-GET requests.

OWASP has a standard library for CSRF protection in Java. If you need stateless sessions, review this example.

This Microsoft article illustrates how anti-XSRF tokens are implemented in .NET.

Express

The express/csurf library implements CSRF protection for Express.

OWASP has a description of how to implement CSRF protection in PHP.

Further Reading