Cracking Drupal - cross site request forgery http://crackingdrupal.com/taxonomy/term/46/0 en Protecting your Drupal module against Cross Site Request Forgeries http://crackingdrupal.com/blog/greggles/protecting-your-drupal-module-against-cross-site-request-forgeries <p>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.</p> <h3>What is Cross Site Request Forgery - CSRF</h3> <p>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.</p> <p>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:</p> <ol> <li>Your site has a list of accounts and presents links to delete accounts. That link points to a URL like <a href="http://example.com/admin/delete/user/2" title="http://example.com/admin/delete/user/2">http://example.com/admin/delete/user/2</a> which contains the account identifier for the account to be deleted (2 in this example)</li> <li>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 <a href="http://example.com/admin/delete/user/2" title="http://example.com/admin/delete/user/2">http://example.com/admin/delete/user/2</a></li> <li>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. </li> </ol> <p>Major bummer.</p> <p>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.</p> <h3>Protecting against CSRF in Drupal with Forms</h3> <p>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).</p> <p>So, how do you build a form? Well, the <a href="http://api.drupal.org/api/file/developer/topics/forms_api.html/6">Forms API Quickstart Guide</a> gets you most of the way there and the small form example in our <a href="http://growingventuresolutions.com/blog/drupal-6-api-cheat-sheet">Drupal 6 API cheat sheet</a> 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.</p> <p>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.</p> <p><em>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.</em></p> <p>So, how do you do a confirmation form? Well the User Protect module did <a href="http://drupalcode.org/viewvc/drupal/contributions/modules/userprotect/userprotect.module?r1=1.52&amp;r2=1.53">just that</a> a little while ago. You can study that patch to learn it.</p> <h3>Protecting actions in Drupal by leveraging tokens directly</h3> <p>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 <a href="http://drupal.org/project/security_review">Security Review</a> module.</p> <p>The token is added to links in the security_review_reviewed function:</p> <p><code><br /> $token = drupal_get_token($check['reviewcheck']);<br /> $link_options = array(<br /> 'query' =&gt; array('token' =&gt; $token),<br /> 'attributes' =&gt; array('class' =&gt; 'sec-rev-dyn'),<br /> );<br /> </code></p> <p>That token is then checked in security_review_toggle_check:</p> <p><code><br /> if (!drupal_valid_token($_GET['token'], $check_name)) {<br /> return drupal_access_denied();<br /> }<br /> </code></p> <p>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.</p> <p>For an example of this being added to a module on Drupal.org you can see the <a href="http://drupalcode.org/viewvc/drupal/contributions/modules/plus1/plus1.module?r1=1.1.4.7&amp;r2=1.1.4.8">patch to fix Plus 1</a> from 2008.</p> <p>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.</p> <h3>Summary on Cross Site Request Forgeries &amp; Drupal</h3> <p>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.</p> http://crackingdrupal.com/blog/greggles/protecting-your-drupal-module-against-cross-site-request-forgeries#comments cross site request forgery csrf Planet Drupal Fri, 11 Dec 2009 04:49:12 +0000 greggles 48 at http://crackingdrupal.com