Protecting your Drupal module against Cross Site Request Forgeries (CSRF)

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: Introduction to CSRF. Now let's learn how to protect it.

Protecting against CSRF in Drupal with Forms

As of Drupal 4.7 (May of 2006) 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.

Making Drupal easier to protect: Drupal 8

There's an issue for Drupal 8 to make it easier to protect links from CSRF by improving the Drupal APIs.

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.