Cross-site request forgery (CSRF) vulnerabilities can be used to trick a user’s browser into performing an unwanted action on your site.
Risks
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.
Ensure Cookies are sent with the SameSite Cookie Attribute
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
Python
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);
}
}
});
Ruby
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.
Java
OWASP has a standard library for CSRF protection in Java. If you need stateless sessions, review this example.
C#
This Microsoft article illustrates how anti-XSRF tokens are implemented in .NET.
Node
Express
The express/csurf library implements CSRF protection for Express.
PHP
OWASP has a description of how to implement CSRF protection in PHP.