CSRF & Authenticity Token
Yann Legendre
This article will introduce you to the basics of a well-known kind of security threat to a website.
It is intended to anyone with very basic knowledge and/or interest in Web development or Web security.
CSRF stands for Cross-Site Request Forgery. Sometimes confused with Cross-Site Scripting (XSS), CSRF is a type of attack where a malicious user uses another user’s specific rights within a website, to make this user perform an action on said website without his/her knowing it, taking advantage of the concept of session. As a matter of fact, CSRF is sometimes referred to as session-riding and I don’t think I can find a most accurate nickname for it to make you understand this concept.
If you are curious about Web development, you probably came across these concepts, and if you are using Ruby on Rails framework, you might have faced this specific error:
The second part of this article is about authenticity token. A simple but smart mechanism provided by Rails framework to prevent CSRF vulnerabilities, we will come back to this later.
HTTP and sessions
Before introducing CSRF, we need to understand a core concept of modern Web which is the session management.
[…] a session is a temporary and interactive information interchange between two or more communicating devices, or between a computer and user.
— Wikipedia
As you can see, the concept of session is pretty generic. In the context of this article, we will be focusing on Web sessions in particular. You might not realise this, but in 2020, sessions are everywhere, and you probably wouldn’t stand using a website without them for more than 30 seconds. A session holds information about the user to enhance navigation experience, this includes remembering the user’s identity to know which parts of the site can be accessed without asking for credentials at every request, languages preferences, acceptance of cookies storing, saved filters, etc.
However, the modern Web is based on the long-standing HTTP protocol (30 years old in 2020, just like me), which basically follows a request-response principle, and does not hold a concept of state. This means if you visit 2 different pages on a website, you will generate two different requests to a Web server, that will in turn render two separate responses, which are completely disassociated. This is why the concept of session has to be handled by another layer.
Let’s take the example of how sessions are handled by a Rails application. When a user visits our application, the application server receives an HTTP request which is first processed by a middleware. (For more information on this topic, you can visit this very well written Medium article, issued by one of my colleagues). This middleware will (among other things) decode the HTTP request into backend Ruby objects, and check the identity of the requester. If the requester is recognised, the response will take into account information suited to the user (user preferences, specific authorisations, etc), along with a session_id cookie.
This session_id cookie supplied by the server is then stored in the user’s browser, which will send it back with each new request, allowing the server middleware to identify the user and maintain a session-managed user experience. Easy right? I suppose you might have already experienced deleting cookies in your favourite browser and being logged out from every website? That’s why.
Why did I hide all my cookie values? Well, anyone sending a request to Github with my user_session cookie would be logged in to my account (actually, GitHub uses other security layers, but you get the point!). Now are you starting to guess how CSRF works?
Cross-Site Request Forgery
To illustrate the concepts, let’s imagine that the Joker is using CSRF/session riding using Batman’s account on Gotham.com.
The Joker knows Batman has an account on Gotham.com with admin rights and wants to take advantage of this. He could use Batman’s session to make him delete his account, impersonate Batman to send a message to someone else, to modify Joker’s access rights, etc.
All he has to do is engineer the request that needs to be sent to the server, make Batman unknowingly send that request using his own session. All it takes is for instance Batman clicking on a disguised link on an image, this kind of malicious methods to trigger user actions as entry point to an attack are part of what we call social engineering. This click sends a request through Batman’s browser with his own valid session_id, and the worst part is maybe he will not even realise it. Et voila. CSRF.
As a user, you should be aware of social engineering techniques, so that you never click on suspicious content.
As a developer, there are a few simple solutions to prevent CSRF on your website:
- use pop-up alerts messages for sensitive actions (e.g. “Are you sure you want to delete this account?”),
- respect Web semantics, use GET, POST, PATCH, DELETE verbs when defining your routes,
- set a TTL (time to live) for sessions, and force more frequent authentication,
- and of course, use authenticity tokens :-)
Authenticity token
We will focus for a while on this mechanism provided by Rails to prevent CSRF. Its goal is to make sure that all information posted to your app server (from POST, DELETE action) comes from a request issued by a form/link/AJAX action coming from inside your website, and not from a maliciously build request sent from a shady link.
When generating a form with Rails, a hidden input field containing an authenticity token is automatically embedded and is expected to be received by the server upon submission. It is unique and renewed with each form instance, making it virtually impossible to reproduce when building a request to attempt CSRF, and therefore making it a very reliable way to authenticate the origin of the request.
How does this work? Well, this is actually pretty straightforward. It can pretty much be resumed with one single method from Rails framework, the ActionPack module to be exact, which role is to handle Web requests and responses.
Here is an extract of ActionPack code source:
def verified_request?
!protect_against_forgery? || request.get? || request.head? ||
form_authenticity_token == params[request_forgery_protection_token] ||
form_authenticity_token == request.headers['X-CSRF-Token']
end
To sum up and spare you all the details, every request goes through this method, and an error is raised if this does not return true.
This means that Rails will allow a request to be passed on if:
- !protect_against_forgery: this means CSRF protection has been disabled for a given controller action. In the vast majority of cases, you will not want to do that.
- request.get? || request.head?: GET/HEAD requests are not subject to authenticity verifications, as they should only be aimed at displaying content, not modifying server content. Hence the importance of respecting semantics defining HTTP verbs correctly. Be careful, using GET requests with parameters to handle simple requests exposes your application to CSRF!
- form_authenticity_token == […]: when we have not bypassed protection against forgery, and the server receives a POST, PATCH or DELETE request. Here, we actually check for the validity of the authenticity token. It can either come from params (token issued from the hidden form input we saw earlier), or HTTP headers (for AJAX requests).
There you go! Hopefully this helped you learn a little more about the wonders of Web sessions, authenticity tokens and Cross-Site Request Forgery (you can’t imagine how many times I have written CRSF instead of CSRF writing this article…). I really enjoyed writing this very first article, and I will be happy to receive any corrections, precisions, or comments. :)
Take care!