I recently found myself in a conversation about the difficulties of building and implementing effective CSRF protection. Not only was I struggling to get across the technical details of a CSRF attack, but there was a big focus on building a 'bespoke' solution.


Cross-Site Request Forgery explained

CSRF can be a fairly simple attack that revolves around tricking a browser into sending a HTTP request by having the user visit a malicious page. I demonstrated this attack against the web admin interface of the EE BrightBox router some time ago, you can read more on that here: EE BrightBox Router Patched - Still Vulnerable.

The attack boiled down to simply issuing a POST request against the router from a malicious page that I controlled.

CSRF form


I embedded the above form into a demo page hosted online under my control and used JavaScript to fire the form submission once the page loaded. We aren't really doing anything out of the ordinary here and the browser has no problem with complying with the request. It sends the HTTP POST request to the router but the crucial flaw is that if the browser has a cookie for the target address, in this case the router, the cookie is sent along with the request. We are, after all, executing this request in the context of the user and their session, if one exists, and abusing the trust that the site is placing in the browser. All this requires is that the user has a valid session cookie for the target, which is quite often the case, or can easily be obtained by adding some pretext about how you will give them some settings to make their internet connection 10x faster if they login and follow your tutorial. The user is logged in, the router trusts requests from the user's browser and you can then issue whatever requests you like against the router as the user. This example wasn't particularly malicious and only rebooted the router, causing no harm, but you could alter the DNS settings to hijack all of the victim's traffic or create a WiFi network for yourself to connect to and abuse their internet connection, for example.


CSRF protection on report-uri.io

Working on the same principle as the demo above, a malicious attacker could embed a form on one of their pages and issue POST requests against the report-uri.io site. These POST requests would be issued from the user's browser and report-uri.io would happily execute the requests as they have come from a user that holds a valid session so far as we can tell. Even if we go beyond checking the session and look at the source IP perhaps, these requests are coming from the user's browser so there is little we can do to distinguish them from genuine requests. The attacker could do things like change your reporting address, enable or disable certain filters or anything else really that only requires a POST request and no other information. The only thing this rules out is a password change, as we require the existing password before we accept the new one. If the attacker already had your password, they'd just login to your account!


Mitigating CSRF

To counter a CSRF attack, you need to make every request against your site require a piece of information that the attacker doesn't have. Much like the password change mentioned above, if there is a requirement for a specific piece of information they don't posses, the request will fail. The best way to achieve this is to embed a unique token into all of your web forms and then expect that token to be returned by the browser when the user submits the form.

Anti-CSRF token


In the above image you can see the anti-CSRF token being implemented on the report-uri.io test site where the user changes the collection filters for incoming reports. The value of the hidden field is a nonce and if the user submits the request back without the token, or with an incorrect token, the request is rejected. This makes the life of an attacker incredibly difficult because they have no way to know the value of this token when they build their form into their malicious page. They can know all of the details of the form like the target, the fields and accepted values except for the anti-CSRF field. Now, if a user falls victim to an attempted CSRF attack, the site will reject the malicious POST request as it does not contain the expected token. You can test this out for yourself now by deleting the anti-CSRF token in the DOM and then trying to submit the form. The server will reject the request and you will get an error message.

CSRF error


Rolling your own is hard

To implement proper CSRF protection on your own could be a great deal of effort. You have to issue a unique token on page load, tie that to the current session and then check for that same token when the form is submitted checking that everything is valid. Not only that but you have to expire tokens once used, expire tokens if they aren't used within their lifetime and keep all of this running smoothly as it effectively has the ability to cripple your site if it goes wrong! Fortunately, I'm using the CodeIgniter MVC framework and implementing CSRF protection was simply a case of adding the following to my config file.

CSRF protection config


The config values are pretty self explanatory. We're enabling CSRF protection, naming the token present in the form, naming the CSRF cookie, setting an expiry time for the token and the regenerate flag forces the regeneration of a new token on every submission. That's it. Seriously. This is one of the huge advantages of using a framework to do the heavy lifting for you. To do this myself would have probably taken considerably more time and effort than adding 5 lines of config!! What's even better is that once you enable the CSRF protection, you don't even need to modify your views. If you're using the CodeIgniter form_open() helper, and you should be, then CodeIgniter will automagically add the form fields for you, no hassle, no work. This is why report-uri.io has had CSRF protection from the beginning and why you should let your framework do the heavy lifting for you.