Cross-site Request Forgery (CRSF)
[Video courtesy of Computerphile]
Cross-site request forgery (CRSF) is one of the top three vulnerabilities in websites today. In a simple example a user may look at the code of your website and copy one of the forms. For example, this form may be responsible for the deletion of a user account. An example hackable form may be generated from a PHP file like so:
<?php session_start(); ?> <html> <body> <form method="POST" action="/user/delete"> <input type="hidden" name="user_id" value="<?php echo $_SESSION['user_id']; ?>" /> <input type="submit" value="Delete My Account" /> </form> </body> </html>
When the malicious user sees the form by clicking "view source" they won't see the PHP, but they will see that the hidden input field is set to an ID of something like 34. They can then create their own form (or use a tool like Postman) that sends POST requests with various user ID's to www.your-domain.com/delete/user in order to delete all the users from your system. Your webserver will accept the requests even though they are not from it's own domain (hence the term "cross-site"), and execute them.
The form handler could check that the user_id matches what is in the session, or just using the ID from the session rather than having a hidden input field in the first place, but this wouldn't solve the scenario where a legitimate user has a session open with your website, and then clicks a malicious link, perhaps in an email or on another site, such as:
<a href="http://www.yourdomian.com/user/delete?user_id=34">Click me</a>
Even worse, hidden images could be used in emails/sites which will automatically invoke:
<img width=0px; height=0px; src="http://www.yourdomian.com/user/delete?user_id=34" />
In these scenarios, the handler will see the
$_SESSION['user_id'] of the legitimate user and delete their account.
How do I prevent CRSF Attacks?
At this point, developers may try to utilize the http referer header to ensure that the POST request came from their very own domain to prevent this sort of attack. Unfortunately this is not really a solution as privacy addons may block this header being sent. Also, the header could also be spoofed by the attacker.
The real solution comes in the form of single-use tokens, often referred to as a nonce. Every time the form is generated, a unique token should be generated and inserted as a hidden input to the form. This token should often be tied to the other fields, the user ID in this case, and "die out" (TTL) after a period of time has passed in which it has been unused. When the form is then submitted, the server should check for this nonce and reject the submission if:
- The nonce is not present in the request.
- There is no such matching nonce in the lookup table.
- The nonce is in the lookup table, but it doesn't apply to the other field values being submitted (e.g. the
user_idto be deleted).
As soon as the nonce has been used, it should be removed from the lookup table to prevent repetition attacks.
Now once such a form is created, a malicious user could navigate to the website and copy the form with it's nonce's several times and not use them. They could then try to submit a manipulated form with different user ID's and these collected nonces, but this would not work as the nonce is not related to the user IDs submitted. This is why it is important that the nonce should be tied to other pre-populated fields in the form.
To make a generic solution, a signature (hashsum) of the pre-populated fields (making sure to include the nonce) could be placed into the form and the lookup table. When the handler receives the request, they will perform a hash of the relevant fields to check it matches the signature that was sent. If there is a mismatch, then one of the fields was tampered with. This means that you don't have to have a lookup table for each of your forms, but a single one to cover all forms. The reason you cannot simply use the signature as a replacement for the nonce is because without the nonce, the hacker could figure out which signature to generate and send from the input fields they have changed, and submit it. This could go through because a legitimate user has loaded the "delete my account" page but never clicked it, thus having a CRSF token entry sitting in the lookup table waiting to be exploited.