CSRF stand for Cross Site Request Forgeries, it’s a method that allows an outside attacker to send malformed HTTP requests to a website, but from a victim’s computer. In this case the actual victim is the accomplice to this attack.
Stronger security measures must be implemented in order to avoid CSRF attacks, and to make sure the website and it’s users are not vulnerable.
To better understand CSRF attacks let’s look at an example. Let’s say you’re signed in to Facebook, you browse around and in the mean time you open a new window or a new tab and visit another site. It’s a typical scenario. Now, your still signed in at Facebook on the other tab and you visit a site where there’s a CSRF attack implemented. Now the CSRF site actually could send out spam to your Facebook friends or even delete your account, all this using your credentials, because a session is saved when you logged into Facebook (remember, on the other tab).
CSRF Attack Example:
1 2 3 4 5 | <form action="process.php" method="get"> <input type="text" name="amount" value="33" /> <input type="text" name="destination_card_number" value="1111222233334444" /> <input type="submit" name="submit" value="Transfer" /> </form> |
Consider the above fictive card payment transfer form. The request method GET is used to simplify the example, but CSRF is not immune against POST requests either.
The actual request would look something like this if it’s posted let’s say to example.com:
http://www.example.com/process.php?amount=33&destination_card_number=1111222233334444
The only thing an attacker has to do is include a request on the “attack web page” (e.g. csrfhacker.com, this is just a fictive site), when you visit this site, the request will be sent to example.com and a transfer would be made with your credentials.
The most common form to fire requests from an attack website is to use the img HTML tag, because before the browser shows you the image, it actually sends a request to the server for that image resource. Now if the attacker includes this on his attack web page:
<img src="http://www.example.com/process.php?amount=33&destination_card_number=1111222233334444" />As a user you can’t do anything against CSRF attacks, as a webmaster you can. Although a webmaster cannot control his website’s visitors, but fortunately he can filter out CSRF attacks just as easily as the attack could be implemented.
Let’s see the security measures a web developer should take in order to successfully knock out CSRF attacks:
1. Protection Against XSS Attacks
You should make sure your website is protected against XSS (cross site scripting) attacks, if not then it’s useless to protect against CSRF, because with any security measure an attacker could bypass this with a counter XSS attack.
Also using POST requests doesn’t eliminate CSRF attacks, it makes a little harder for the attacker, and this is the point.
2. Using a Form Token
The root problem with CSRF vulnerability is the failure to verify the source request. A good web developer should already know that by simply relying on $_SERVER['REMOTE_ADDR'] is not effective, almost useless. With the help of a form token we could verify the request’s source.
A token could be a simple random number that can be injected in the target form as a hidden input element, and also saved in the session when the form is rendered. When the form is submitted the token from the request can be compared to the token that is saved in the session. If the two doesn’t match the form should be discarded.
Generating a form token in PHP is easy:
1 | $token = md5(uniqid(rand(), true)); |
Implementing the token is also easy, just save it in user’s session and inject it in the target form:
1 | $_SESSION['token'] = $token; |
1 2 3 4 5 6 | <form action="process.php" method="post"> <input type="hidden" name="token" value="<?php echo $token; ?>"> <input type="text" name="amount" /> <input type="text" name="destination_card_number" /> <input type="submit" name="submit" value="Transfer" /> </form> |
When the request was sent the token should be compared like this:
1 2 3 4 5 6 | if ( (!empty($_SESSION['token'])) && (!empty($_POST['token'])) ) { if ($_SESSION['token'] == $_POST['token']) { //process the form } } |
3. Using Form Timeout
Using form timeout adds extra security to the form and to the web application itself, which is always a good thing to do.
Form timeout consists of saving the time when the form is rendered and compare it to the time when the form request was sent to the server. From this time-frame we could filter out potential attacks, not just CSRF, but also spam bots too.
Setting a maximum time-frame in which the form must be submitted is recommended, but don’t forget to also set a minimum time-frame in which if the form is submitted would be discarded, because we’re dealing with either spam bots or an automated CSRF attack. It is unrealistic that a human can submit a form with 4 – 5 fields under 2 seconds.
To implement this security measure the following code or similar is required:
1 | $_SESSION['time'] = time(); |
When the request was made the comparison should look like this:
1 2 3 4 5 6 7 8 9 10 | $min_allowed_time = 20; $max_allowed_time = 60; if ( (time() - $_SESSION['time']) < $min_allowed_time ) { echo 'form discarded'; } if ( (time() - $_SESSION['time']) > $max_allowed_time ) { echo 'form timeout'; } |
If you use the token and the session it's still possible to CSRF. If the CSRF-Script visits the site just before you send the request you can include a valid token into the data you send and it is useless. A session is also created. Adding a delay is also possible without any problems. If you do this via JS the session and the token should both be assigned to the client. Something like:
// Pseudo-Java-Code // function names should explain what they do well enough String host = "example.org"; String html = getHTMLCode(host); String token = extractToken(html); Thread.currentThread().sleep(500); sendData(host, any, data, with, valid, token);Or is there an error in my reasoning? With kind regards Simon
It's a fact that $_SESSION in it's default state is not very secure, there's the problem with session hijacking, etc. which is out of the scope of this article.
But if you properly secure your session (e.g. save it in a database, etc.) than I can't see how the attacker could find out the random token which is injected in the form.
Great reading and a best practice for everyone. I would have also added an example pointing out the HTTP Headers exploiting on let's say an image link but that comes at a different angle I suppose.
Also people should drop using the $_REQUEST variable since it is not secure but frequent unfortunately. What do you think about AJAX requests, how would you secure those?
Yes, people should indeed drop using $REQUEST, instead use $GET or $_POST.
Do be honest, I never thought about AJAX CSRF attacks, but now you mentioned it it gave me a few ideas.
First and foremost the server should not accept any AJAX requests coming from other domains. Disabling GET if possible, and checking for XML and JSON hijacking.
I also found a good post on the subject: eweek.com - AJAX Apps Ripe Targets for Javascript Hijacking
Ajax Requests should not make CSRF-ing more accessible, at least not in theory but when attacks through ajax do succeed they would have to be far more deadly if you ask me, so a full article on this would help quite a few people.
Until than though, I will submit an article on session hijacking later on today, maybe it will be of any help to others :) PS: How on world do you it paragraphs here?
As far as I can tell, the only way to secure your sessions/cookies or whatever is SSL. Once they have your credentials it's game over or your site is unusable to legitimate users. The real divide is over what needs encryption and what does not.
Partly true, but not everybody has access to a valid SSL certificate.
On the other hand if you store your session info in a database and only save the session ID in the cookie it's basically secure if the attacker can't hack your database.