Cross-site request forgery (CSRF) is a widely exploited vulnerability in websites where malicious requests are transmitted from a user who is trusted by the web application. CSRF attacks target state changing requests, not theft of user data since the attacker has no way to see the result of the request.
This article describes the attack, how to defend your website and also your users.
Understanding how an attack is carried out
Before I explain how a Cross Side Request Forgery attack (CSRF) works, I must explain how login systems work on websites.
The world wide web uses the HTTP protocol to communicate between a client (the browser), and the server. HTTP is a stateless protocol, the webserver does not hold a state of a visitor, so it cannot distinguish one user from another.
To address this issue, cookie technology was invented in 1994. The webserver instructs a browser to hold a unique key which the browser sends back to the webserver on every request.
For example, Alice logs in to her account on the website of her bank, the server sends back a cookie with a unique key for Alice which the browser stores. When Alice navigates to the page which displays her account balance, the browser sends the cookie along with the request. The server identifies the cookie as it is Alice’s cookie, and Alice is logged in again on the website.
An attacker can use this behavior to do a request to another website on behalf of the logged in user.
This is best explained in an example:
User Alice is logged in to her bank account in her browser. At the same time Alice is tricked into visiting Bob’s malicious website by, for example, a phishing email.
Bob placed a copy of the money transfer form of Alice’s bank on his website. When Alice visits Bob’s website, the website posts the form to the website of Alice’s bank.
Because Alice is logged in, her browser sends her cookie information to the bank website. Alice is now logged in and the form is executed, transferring money to Bob.
<form action="https://alices-banking-site.com/account/transfer-money" method="post"> <input type="hidden" name="Transaction" value="withdraw" /> <input type="hidden" name="Amount" value="1000000" /> <input type="hidden" name="To" value="NL91 ABNA 0417 1643 00" /> </form>
In this image the malicious website is the website of Bob. De bank website is the website of Alice’s bank.
In security terminology, abusing this browser behavior is known as a Cross-site Request Forgery attack. It is carried out by misusing a session belonging to an authorized user.
As a website user, can I prevent this?
Modern web browsers like Chrome or Firefox have the ability to block cookies. This makes you totally immune for this kind of attacks, but your browser experience will be very poor since you can not log in to any website anymore.
Another way is to never open more than one website at a time and logout when you are done.
As a website developer, how can I protect my users?
There are a few ways to prevent CSRF attacks which can be implemented by the website developer.
Check the origin of requests.
When receiving a request, we have potentially two pieces of information available in the HTML request header to check the origin of the request. These are the referer and the origin fields.
You can check one or both fields to check if the request originated from another source than your website URL. If not, you can discard the request.
Checking these fields give some protection but the fields may not always be present.
Example request to vicompany.nl
Accept: text/html;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: nl,en-US;q=0.9,en;q=0.8,en-GB;q=0.7 Cache-Control: no-cache Connection: keep-alive Cookie: _ga=GA1.2.1691126654.156983654; Host: www.vicompany.nl Pragma: no-cache Referer: https://www.google.nl/ Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Add Anti-CSRF tokens to your forms.
Generate an unguessable key on the server, which is unique per request, and add this key as a hidden field to the forms on your website. When the form is submitted you can check if the key of the posted form is the same as the key which is generated by the webserver. If not, you can discard the request.
The idea is that a malicious website is unable to generate a key which will be accepted by the webserver.
This method works well but when your website is vulnerable to XSS attacks, the attacker can read the hidden value from a genuine request.
This strategy doesn't work for GET requests since the anti-CSRF tokens are added to forms, which are in general send by a POST request.
Add the Same-Site property to the cookies you issue.
The same-site property on a cookie tells the browser to not send cookie information to the website along with cross site requests.
There are two possible values for the same-site attribute, Lax and Strict.
In Lax mode some cross-site usage is allowed like GET requests. Strict blocks all cross-site usage, even when the user is browsing to another website by clicking on a link.
Unfortunately, not all browsers support this cookie property. If you want to know if your browser supports this property, visit caniuse.com.
To prevent CSRF attacks on your website I would recommend implementing the Same-Site cookie attribute. It is not supported by all browsers, but it is backwards compatible: it does not break anything when the attribute is not supported.
As long not every browser is supporting the Same-Site cookie attribute I would also suggest implementing the anti-CSRF token pattern. It is a little bit more work but is a secure defense mechanism against CSRF attacks.
The defense mechanisms against CSRF attacks in this article will only work when the state changing actions on your website are solely accessible via a POST request. (Except for the Strict same site cookie).
So make sure that actions like logout are not available through a GET request.