Frank DENIS random thoughts.

Preventing cross-domains scripting attacks

Cross-domain scripting vulnerabilities, also known as XSRF or CSRF, are very common.

The idea is simple. Let’s say the example.com web site has user accounts. In order to change his password, a logged user fills in a form that submits the data to http://www.example.com/change-password.php

But what if a malicious web site forces you to submit data to that URL, something trivial with javascript? If you were logged on example.com, your password can be changed by the malicious web site. This can happen in a hidden iframe so that you don’t even notice that something happened.

An easy way, which is still very effective in most cases is to check for a secret value when reading the form data. The script that displays the form adds a <input type=”hidden” …> field with a secret value, and the script that processes the form data check whether that value is right or not.

It’s often made with a HMAC of the fields content, or with a secret key stored in the session, or with a HMAC of user ip, or things like that. Why, but why make it so complicated? You already have a secret key: the user session id.

As noticed by PDP, it can be as simple as changing:


<input type="text" name="name" value=""/>
<input type="text" name="lastname" value=""/>
<input type="submit"/>

to:


<input type="text" name="name" value=""/>
<input type="text" name="lastname" value=""/>
<input type="hidden" name="CSID" value="<?php echo htmlspecialchars(session_id()); ?>" />
<input type="submit"/>

And then, check for the session id while processing the form:


if (empty($_POST['CSID']) || $_POST['CSID'] !== session_id()) {
  die();
}

Of course, this require changes to your existing code, but it’s still easy to implement.

If you don’t want to change your existing code, another approach is to user HTTP_REFERER. Of course, HTTP_REFERER is something that users can easily change, it’s also something that gets cleared while using HTTPS. This is not a trustable value. But in the real life, the following piece of code is enough to stop common XSRF attacks.


# List of authorized domains, for instance : 'localhost|example.com|c9x.org'
define('AUTHORIZED_XSRF_DOMAINS', 'localhost');

function _anti_xsrf() {
    @ini_set('url_rewriter.tags', '');
    if (empty($_POST) || empty($_SERVER['HTTP_REFERER']) ||
        preg_match('#^http(s)?://([^/]+[.])?(' .
                   preg_quote($_SERVER['HTTP_HOST']) .
                   '|' . AUTHORIZED_XSRF_DOMAINS .
                   ')($|/)#i', $_SERVER['HTTP_REFERER']) > 0) {
        return;
    }
    foreach (array_keys($_POST) as $k) {
        unset($_REQUEST[$k]);
    }
    $_POST = array();
}

Just insert this code in a file that gets included at the beginning of your scripts. The idea is simple: if POST data is coming with the same HTTP_REFERER as the current page, it will pass through. If it’s not, and if the HTTP_REFERER value is not explicitely listed in AUTHORIZED_XSRF_COMAINS list, POST data gets cleared. This is the code I use on many web sites with great success. Once and again this is not the ultimate XSRF blocker, but it’s easy to add to any web site and it’s still a very effective barrier against common attacks.