Cross-Site Request Forgeries (CSRF)
Today I want to cover a kind of security issue that is not addressed very often. Just about any book or article aimed at developers has some warning about XSS and SQL injection. Those two attacks are arguably two of the most harmful, but there are certainly other things to be weary of. In this post I will talk about Cross-Site Request Forgeries or CSRF ("see-surf").
CSRF Explained
CSRF is a type of exploit that allows an attacker to send a request to your application with the authority of another user and without that users consent. Sometimes these requests might mean little (i.e., the user is a guest so they can't do any harm) but other times the requests can be very dangerous and destructive (i.e., the user is an administrator).
How It's Done
There are a number of ways that a request can be made against your server without any user interaction. The most basic and probably most often used technique is with a simple HTML image tag:
-
<img src="http://example.org/somescript.php" width="1" height="1" />
Even though the script handling the request ("somescript.php") might not be a valid image, it doesn't matter. The request is still being made. And by providing a width/height of 1 pixel, we can be certain the user doesn't even suspect anything is wrong.
Other ways might include using an iframe in the same way, or using automatic redirects.
How It's Dangerous
By being able to make requests so easily and without any interaction on the users part leaves services open for attack. As an example say we have an administrators control panel that lets admins delete articles on a website. An attacker might somehow get an admin to visit his website with an image tag on it:
-
<img src="http://example.org/admincp/delete_article.php?id=42" width="1" height="1" />
The danger comes when the admin happens to have an active session. If the session is controlled by cookies or IP address etc. then this request will be made with all of the authority of the admin. It would be as if the admin made the request purposefully.
You might be thinking that it would be a simple matter to prevent such an attack by using POST data instead of GET data. If you used POST data then the ID in the URL would not be accepted. While it does create another barrier for a potential attacker (which is always good), it doesn't solve the problem. Consider this code fragment:
Placing a 1x1 iframe with that code in it would achieve almost the same result as the 1x1 image, but this time using a POST request. Using POST over GET adds little security in this regard.
Protecting Against CSRF Attacks
There are a few general practices you should employ:
- Always use POST for important actions. This doesn't provide much protection (see above) but it does add another small barrier for an attacker to overcome. And it is generally good practice as it prevents users from performing harmful requests too easily (i.e., pasting a URL to a friend, bookmarks, etc).
- In some languages like PHP, you can get all incoming data in a common way. For example, in PHP you can use the $_REQUEST superglobal to get data from cookies, GET or POST. You should avoid the use of such convenience methods and always specify a specific location for where values should be coming from.
- Don't rely on confirmation forms or confirmation pages. The attacker will simply modify his request to indicate a "confirmed" action. For example, he might only need to add &confirmed=1 to the URL or add a "confirm" hidden field to a form. Confirmation pages are only effective when preventing human error, not for preventing attacks.
Easy: Deny Off-Site Referrers
One easy way to prevent CSRF attacks is to deny all POST requests from off-site domains. But this solution has a problem of its own: Not all users send the referrer header.
Every browser should send a referrer with each request, but there are dozens of hacks, mods or plugins that lets a user turn this feature off (some people see it as a privacy concern). So for these users you have a couple of choices: The first is to deny these users outright (probably not advisable except for special circumstances), and the second is to only enforce the referrer requirement when the referrer was in fact provided.
I choose to use the latter of the two choices. Since most users do provide a referrer, it is a great way to at least prevent the majority of people from falling victim to CSRF attacks. If the user does not provide a referrer, then we can rely on other means of protection (read more below).
Also a problem exists with the so-called dereferer services. There are some services available (and some proxy sites) that intentionally change the referrer of requests that run through them to protect the identity of the people using them (i.e., changes example.org to otherexample.net). Most internet security suites blank out the referrer, but that is easy to handle by just specially handling blank or missing referrers. If a user is using a dereferer service then there's no way for you to know if the request they make is real (and the referrer was modified intentionally) or fake (and the referrer is from an attacker site).
It's up to you to decide if these problems are worth the added security. What I do is to auto-pass all blank or invalid referrers so I can rely on other protection mechanisms (see the next section). The problem with dereferers is small, not many people use them. So I count it as an acceptable risk when important functionality is concerned.
Here is a function you can use in your own applications that will return false when the referrer is invalid. You can optionally supply a whitelist of domains that are allowed, other then the current working domain:
-
/**
-
* Check the users referrer on POST requests to make sure
-
* they come from the correct domain.
-
*
-
* @param array $whitelist An array of domains that should be
-
* allowed regardless of the current domain.
-
*
-
* @return boolean True if the referer is allowed, false otherwise.
-
*/
-
-
// Only POST requests should be checked
-
return true;
-
}
-
-
-
-
#------------------------------
-
# Get the referrer host
-
#------------------------------
-
-
$ref_host = false;
-
-
$ref_host = $ref_parts['host'];
-
}
-
-
// No referrer, default to true
-
if (!$ref_host) {
-
return true;
-
}
-
-
-
#------------------------------
-
# Get the current host and add
-
# it to the whitelist
-
#------------------------------
-
-
$host = false;
-
-
if ($_SERVER['HTTP_HOST']) {
-
$host = $_SERVER['HTTP_HOST'];
-
} else if ($_ENV['HTTP_HOST']) {
-
$host = $_ENV['HTTP_HOST'];
-
} else if ($_SERVER['SERVER_NAME']) {
-
$host = $_SERVER['SERVER_NAME'];
-
} else if ($_ENV['SERVER_NAME']) {
-
$host = $_ENV['SERVER_NAME'];
-
}
-
-
if ($host) {
-
}
-
-
// If for some reason there are no allowed hosts,
-
// default to true
-
if (!$whitelist) {
-
return true;
-
}
-
-
-
#------------------------------
-
# Check the referrer against
-
# allowed domains
-
#------------------------------
-
-
$valid = false;
-
-
foreach ($whitelist as $check_host) {
-
-
-
// Check with a space on each side to easily
-
// anchor it without using regex
-
-
if (stripos(" $ref_host ", " $check_host ") !== false) {
-
$valid = true;
-
break;
-
}
-
}
-
-
return $valid;
-
}
Example usage:
-
<?php
-
if (!check_post_referrer()) {
-
}
-
-
// Process form
-
// ...
-
-
?>
A small aside here: Note that the header for the HTTP referrer is spelled incorrectly as "referer" (hence a missing "r"). This somehow made it into the HTTP standard. If you implement your own version of this function be sure you intentionally misspell "referer".
Note that this method is quite effective but is still not the holy grail. Referrer headers can be spoofed by an attacker in a couple of different ways. For example, in Flash the attacker can send a completely custom request to the server including a specially written referrer (though as I understand it, the latest version of Flash does not allow the altering of certain headers including the referrer). Other technologies may be prone to such abuse like Java applets or ActiveX controls.
Effective: Unique Tokens
The referrer check described above is easy to implement on a global scale, but as I explained, has some pitfalls. A more effective way to prevent CSRF is by authenticating each form with a special, unique token. This method is very effective and suffers from virtually no workarounds, but it's takes a bit more work for you as the programmer.
Basically you create a temporary token that is unique. Each time the user views a form, you generate another unique token. Once the user submits the form, you check the unique token to make sure it is valid. This makes it very difficult for an attacker to submit a form since there is no way he can get the unique token correct. If the token provided is incorrect, then you simply do not process the form.
Here's a simple implementation using sessions:
-
<?php
-
-
-
$_SESSION['token'] = $token;
-
-
?>
-
<form action="somescript.php" method="post">
-
<input type="hidden" name="token" value="<?php echo $token; ?>" />
-
<!-- The rest of your form -->
-
</form>
And when you process the form, you check to make sure the token in the POST data is valid:
-
<?php
-
-
if ($_POST['token'] != $_SESSION['token']) {
-
}
-
-
// Process form
There are different ways of saving the token of course. An alternate version might be to insert tokens into a database and then delete them after use. An advantage of this method is that a user can load multiple pages at the same time, all of which can have valid tokens. Using the session script above, the token will be overwritten on each subsequent page load. In todays world of multiple windows and tabbed browsing, it might be worthwhile to allow multiple tokens to co-exist. The only important aspect is to ensure that a token expires (either it can be used only once, or it has a lifetime, or both) and that it is unguessable.
Using the token method of protection does mean you have to create some way of generating tokens on every form and checking the validity of the tokens whenever you process a form. This can introduce some extra work for you, but it is well worth the effort. I'm sure you can think of ways to make an efficient token system that simply plugs right into your template system and form validation scheme.
Conclusion
You've certainly given lots of thought to XSS and SQL injection, the two most talked about vulnerabilities in web software. Today I hope I've either introduced you to a third big security issue or at least provided you with some information you didn't think about before.
Check your apps for CSRF vulnerbilities, they might be a little hard to notice until you really think about them. But even the big guys make mistakes. A while back, the major social news site digg suffered from this kind of attack. The site works by allowing users to "vote" on news stories to decide if the story is popular. Spammers used a CSRF attack to make any user that visited their site automatically vote for their story. Protect yourself! Use the techniques described in this post to add security to potential attack points in your applications.
Did you enjoy this post? Why not leave a comment below and continue the conversation, or subscribe to my feed and get articles like this delivered automatically each day to your feed reader.

Trackbacks & Pingbacks
[...] http://devlog.info/2007/09/02/cross-site-request-forgeries-csrf/ [...]
Comments
Leave a comment
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>