How to prevent Cross-Site Request Forgery

Checking HTTP header Referer

Web browser automatically sends with each HTTP request a header Referer whose value is the address of the page from which the request was made. For example, when user passes from en.wikipedia.org to twitter.com, web browser will automatically send the header Referer: https://en.wikipedia.org/.

Checking that the value of the Referer field is the same as the current site is not valid. Sending the Referer header can be disabled in the browser, for example, at the Incognito mode, which will block the loyal users.

Same-Site attribute Cookie

At the moment, Google are working on at specification "Same-Site" Cookie attribute. The attribute makes it possible to explicitly indicate that the Cookie does not need to be transmitted if the request comes from an origin different from that which Cookie has installed. Setting this attribute gives automatic protection against CSRF attacks. The standard has not yet been accepted and most web browsers do not support this attribute.

Checking CSRF token

In process of sending a page to a user, the server generates a string, called a CSRF token, that satisfies the following properties:

  • Unique value for each user session
  • Time to live after which it becomes inactive
  • An attacker does not need to know how to generate a token

The token must be sent along with every HTML form and HTTP request (GET, POST). When the request is received, the server checks the token and if it is correct, the request is processed.

HTML forms support only two HTTP methods. There are GET and POST. There is no way to execute a request with another method (PUT, DELETE) without using Ajax, which requires CORS of the header when accessing another source.

The original form of transferring money to bank.kom is converted to the next

<form method="POST" action="/transferMoney">
    <input type="number" name="accountNumber"/>
    <input type="number" name="amount"/>
    <input type="hidden" name="csrf_token" value="e00e1b12789e4e8d1e3582051d23a1ec"/> <!-- CSRF-token -->
    <input type="button" value="Transfer money"/>
</form>

Generation methods of CSRF tokens

Using an additional data storage

This is the most obvious, but not the most effective method. The server generates a random sequence of symbols, and it is stored in the database (file, noSQL) for the current user and sent to the client at the time of authorization. Then, when the request is received, backend checks that csrf_token is present in the data storage and its lifetime has not expired. The disadvantages of this approach:

  1. Additional data storage load
  2. Additional place to store tokens
  3. You need to delete the old tokens after the expiration of the lifetime

Without using additional storage

In order not to store CSRF tokens in data storage, you need to create an algorithm for generating a token from data that an attacker can not get. For example, using a user ID and a string (Salt), which only the server knows:

Salt = getConfig('csrf.salt');
csrfToken = md5(Salt + UserId);
  • Salt is a random string for the web application specified in the configuration file
  • UserId is user ID of website. It can be a session identifier, derived from a user session (Cookie, JWT).

It remains to add the lifetime for a token. Let a token have a lifetime of 30 minutes. Let's change the process of generating a token as follows:

expirationTime = now() + 30 minutes;
csrfToken = md5(Salt + UserId + expirationTime);

You might think that now you need to store expirationTime on the server. But additional data is simply passed by parameters on the frontend and added to the form by hidden fields.

<form method="POST" action="/transferMoney">
    <input type="number" name="accountNumber"/>
    <input type="number" name="amount"/>
    <input type="hidden" name="csrf_token" value="e00e1b12789e4e8d1e3582051d23a1ec"/> <!-- CSRF-token -->
    <input type="hidden" name="csrf_expiration_time" value="2017-02-22 12:30"/> <!-- expirationTime -->
    <input type="button" value="Transfer money"/>
</form>

Then on the server a token is calculated from the available data, and checked it lifetime csrf_expiration_time >= now().

  Quiz →