Protecting your Drupal module against Cross Site Request Forgeries
Cross Site Request Forgeries (CSRF) are the 3rd most common vulnerability in Drupal and yet they are quite easy to protect against. The precise solution depends on where the problem is, but is never too complex to implement. To start, of course, we need to understand what CSRF actually is.
What is Cross Site Request Forgery - CSRF
When you take an action online - submitting a blog post, delete a photo, registering on a site, transferring money in a bank account - you are making a request to a site to perform that action for you. Several things happen automatically when you make a request to a site: lots of data is sent using either a "POST" or "GET" method and your browser sends along any cookies you have for the site. The cookies often contain your session identification for the site which is how the site knows who you are and what permissions you have.
A CSRF occurs when a malicious user can trick you into making a specific kind of request to a site that you did not really intend to make AND when the site performs that action. A simple example follows:
- Your site has a list of accounts and presents links to delete accounts. That link points to a URL like http://example.com/admin/delete/user/2 which contains the account identifier for the account to be deleted (2 in this example)
- A malicous attacker determines that you are an admin on the site and becomes your "friend" on a social networking site like twitter. They post a message to twitter @your_attention with a TinyURL that redirects to http://example.com/admin/delete/user/2
- By the time you realize where the redirect is going, you've already deleted account #2. Depending on the system that may also delete all of the content associated with account #2.
Major bummer.
The solution to this problem is a secret value associated with the action that is not easy to calculate called a token. When the form or link to take an action like deleting the account is sent to the browser the site sends along a string that includes something unique and only known by the site and the user. When the request is submitted the site validates that the token is still present and is appropriate for the action being taken. If it doesn't validate, the action is denied.
Protecting against CSRF in Drupal with Forms
As of Drupal 4.7 there was a Form API in Drupal core which people used to create forms. This form API provided a central place to do form creation, validation, and submission processing. Part of that creation and validation is to ensure that the special "token" is sent to the user and validated on form submission. Drupal uses a "hash" that is composed of at least three values: the user session ID, a string associated with the action being taken, and a string that is private to the site. If an attacker could figure out this token then they could add it to the request and force the site to take the action. Fortunately, this combination of information cannot be calculated by a typical malicious attacker (they would need a super computer and an additional weakness like session fixation...pretty unlikely).
So, how do you build a form? Well, the Forms API Quickstart Guide gets you most of the way there and the small form example in our Drupal 6 API cheat sheet should finish it off. So, you use forms for every major action on the site that changes data and you will be protected against CSRF.
Some of the time a form won't work in the design or user interface. You just want a tiny little link. So, if you want to use a link for an action like deleting a user or deleting content that is really destructive then you should use a confirmation form. It makes sure the user wants to take the action and acts as a small confirmation.
Side note: usability people say we should not do confirmation forms and that is right, but their concern is flow and their solution is to simply "archive" in an easy to undo way. Our point is to get a form token in there.
So, how do you do a confirmation form? Well the User Protect module did just that a little while ago. You can study that patch to learn it.
Protecting actions in Drupal by leveraging tokens directly
So, what if the action being taken isn't as extreme as deletion? In that case you can use an alternate trick that directly leverages Drupal's token generation more directly. A great example of this comes from the Security Review module.
The token is added to links in the security_review_reviewed function:
$token = drupal_get_token($check['reviewcheck']);
$link_options = array(
'query' => array('token' => $token),
'attributes' => array('class' => 'sec-rev-dyn'),
);
That token is then checked in security_review_toggle_check:
if (!drupal_valid_token($_GET['token'], $check_name)) {
return drupal_access_denied();
}
There are a variety of ways to actually insert the token into the link and write the Ajax code to submit the request and then validate requests. This just shows the basic idea: you add a token into the page that will be returned with the request for the action and then validate it before performing the action.
For an example of this being added to a module on Drupal.org you can see the patch to fix Plus 1 from 2008.
One special note: many people feel that it's semantically inappropriate for a site to take actions in response to links (which initiate an HTTP GET request as opposed to a POST request which is commonly used in forms and which are defined in the HTTP spec as being associated with changing data on the server). There are also potential problems with using links such as http accelerators that pre-fetch all the links on a page. You can mitigate the pre-fetchers by adding a rel="nofollow" attribute to the link, but really the pre-fetchers are relatively uncommon these days so hopefully you don't have to deal with them.
Summary on Cross Site Request Forgeries & Drupal
Protecting against CSRF in Drupal is not particularly hard to do, but to date there have not been any easy recipes on how to handle it. Hopefully this resource will enable people to write their code properly the first time and fix any vulnerabilities you may find in existing code.


Comments
Submission
Provided you only take "actions" in the submit handler (or if you know it ran already). #576276 is a proposal to prevent accidents with validation handlers.
I've recently moved to expose
I've recently moved to expose modifying actions exclusively as forms for exactly the http accelerators you mention. The off-chance that an important site configuration goes bye-bye because somebody visits the site with an accelerator enabled is still scary (Here's a post from '05 by 37 signals reporting how Google Web Accelerator nuked Backpack pages ).
Thank you for the writeup!
W3 guidelines for handling arguments
There's also the issue that you shouldn't be using GET arguments for manipulating, especially deleting, data. Remember the snafu in 2005 when Google's Accelerator engine started causing problems on sites that had all of these Delete actions available as just standard links (with / without JS "confirmation") rather than via POST arguments (i.e. a form submission)? This seems like part of the same problem - you shouldn't be doing destructive actions by using GET arguments.
D7 GET protection
There's an issue now for Drupal 7 to add token support in url(), l(), and hook_menu(). http://drupal.org/node/755584
Post new comment