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 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!
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.
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.
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.
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.
Short URL: https://scotthel.me/csrfp