Auditing Drupal Modules for XSRF Vulnerabilities
About XSRF
Cross site request forgery (CSRF (pronounced sea-surf) or XSRF) is a trust exploitation that shares many similarities with cross site scripting (XSS). The subtle difference is that in a cross site scripting attack the client's trust of the server is abused, whereas with a XSRF the server's trust of the client is abused. For example, in a XSS attack the user trusts that content received from a server is reliable, when in fact it contains malicious scripting that executes on the clients browser. In a XSRF attack the attacker injects script into a page that causes the browser to send requests to a server without the user's consent. This usually takes the form of a silent form post or URL request. Due to the nature of cookies and browser authentication any requests (GET or POST) sent to a server are automatically sent with associated cookies for the domain of the request. This means that if a user logs into a service, and receives a cookie that designates them as authorized, a common practice in web applications, then any future requests to the site carry the authentication cookie. In a typical XSRF attack the users authenticates to one site, gets a cookie, then browses to a site containing malicious code. That code forces the browser to make a request to the authenticated site, which passes in the background, along with the authentication cookie. The server sees the cookie and trusts the user and allows them to carry out activity. Thus, the server's trust in the client is exploited by a malicious third party, without the users knowledge.
In a typical XSRF attack a malicious user could do things like force a user password reset, change a user contact e-mail, or otherwise attempt to alter user credentials in order to allow the attacker access to a service. An attacker might also try to exploit a users status to post new material to a site, or alter existing material to inject malicious content, hijacking the authority and authorization of the victim. This can have extremely dangerous consequences.
Mitigating Factors
The main weakness of the XSRF attack is that is a passive, blind attack. That is to say that the attacker must get the victim to browse to malicious content after authenticating to the target service. In this way the attacker is passive - they deploy their payload and wait for a victim browser to wander past. The attack is also blind in the sense that the attacker probably does not have access to the resource they are attempting to exploit and must guess at the values needed to cause the desired behavior on behalf of the victim. This makes XSRF particularly difficult. Attackers have an advantage with open source targets in that they can browse source code to discover vulnerable targets and tailor XSRF attacks to desired victims.
Example XSRF
In order to make more sense of all of this let us consider a specific example. Suppose we have an application that allows administrative users to delete user accounts by calling a certain URL with specific parameters. This could be target.tld/admin.php?action=delete&user_id=4. Calling this URL would result in a script looking at the user's PHP session ID and determining if they were already authenticated to the service. If so the script would delete the user account with the ID of 4. Users would be required to authenticate to the service beforehand which would result in their account being looked up and various session based attributes assigned to them.
This example is ripe for a XSRF vulnerability. Whenever the user calls the application their PHP session ID is silently transmitted to the site in a cookie. This cookie typically persists for quite a long time, often a day or more. Assuming an admin authenticated to the site, performed some routine maintenance, then browsed away to other websites they can become a victim. If such a user browsed to a site that contained a piece of JavaScript that silently included the page target.tld/admin.php?action=delete&user_id=4 in a hidden iframe the victim's browser would make a call to the target.tld/admin.php page. This request would be appended with the appropriate cookie. The target.tld server would examine the cookie, verify that it belonged to an admin user, and silently carry out the requested delete.
Defense
Given this exploit path, how can web applications be defended? The easiest way to remediate this vulnerability is to require a unique token be generated with every form that is composed to create a modification. For instance, let's say there is a form that allows a user's contact e-mail to be updated. With every request to compose the form a token is generated, tied to the session ID, and given an expiration. When the form posts the application then looks for a token, then looks that token up and makes sure it corresponds to the appropriate session id and has not expired. This means that for an attacker to exploit the form they must be able to predict a token. What's more, unless the user has requested the form to do the modification such a token will not even exist in the application data stores, making the attack impossible.
Examples in Drupal
Drupal takes care of this protection using their form API. Any data manipulated through a form in Drupal is protected from XSRF by default. This protection is not complete, however. There are many areas in Drupal that remain vulnerable to XSRF attacks. The increasingly prevalent area for exploitation is via AJAX functionality. Often times AJAX calls are made outside of the Drupal native forms API and don't include the Drupal token. Furthermore, many AJAX functionality is exposed at a URL devoid of form defenses. Some modules defend against this type of vulnerability by implementing a Drupal forms like token. One such module is the Flag module. Looking at the Flag module version 6.x-1.2 flag.module page you can see on line 442 how this is handled:
/** * Menu callback for (un)flagging a node. * * Used both for the regular callback as well as the JS version. */ function flag_page($action = NULL, $flag_name = NULL, $content_id = NULL) { $js = isset($_REQUEST['js']); $token = $_REQUEST['token']; // Check the flag token, then perform the flagging. if (!flag_check_token($token, $content_id)) { $error = t('Bad token. You seem to have followed an invalid link.'); } else { $result = flag($action, $flag_name, $content_id); if (!$result) { $error = t('You are not allowed to flag, or unflag, this content.'); } } [...]
You can clearly see how the Flag module utilizes a dynamically generated token to ensure the trustworthiness of the client request. Unfortunately some modules do not implement this type of check. One recent example is the Smileys module versions prior to 5.x-1.2. Browsing the CVS repository changes you can clearly see the inclusion of the Drupal forms API to correct XSRF vulnerabilities in http://drupalcode.org/viewvc/drupal/contributions/modules/smileys/smileys.module?r1=1.47.2.13&r2=1.47.2.14&pathrev=DRUPAL-5. By relying simply on a callback to the smileys_admin_delete function in the 5.x-1.1 version the developers removed the XSRF protection provided by the Drupal forms API. This opened the possibility of a malicious attacker including a script on a page that an authenticated Drupal admin could view and cause a URL request to fire off and perform deletes without any intervention.
What to Look For
When auditing Drupal code it is important to check all the URL callbacks (generally set in the 'path' specifications in hook_menu). Be especially vigilant for AJAX functionality as it generally handles URL requests directly without any Drupal forms API intervention. AJAX functionality is often overlooked when considering security abuse and protection mechanisms. Also look for any functionality that allows the user to perform actions by clicking on links rather than filling out forms (such as with the Flag module).
Reporting Findings
Once you've identified a XSRF in a Drupal module it is important to report the problem directly to the Drupal security team. Resist the temptation to report the problem directly to the module maintainer. The Drupal security team will take care of coordination with the maintainer and the release of a fix along with a security announcement on the main Drupal site. Including an exhaustive report of the problem, including a proof of concept, or path to exploitation, as well as a technical description of the problem including line numbers of code that causes problems, or even a patch, will greatly increase the responsiveness of the Drupal security team. Be prepared to get nothing from the team but a canned response and a ticket number. Sometimes it may take some prodding to get an actual response and commitment from the team but in general they are good about addressing issues. If you don't get a response follow up and include a timeline to disclosing the flaw. If you still don't get a response after a week or two then you may want to disclose the vulnerability publicly. The best way to do this is to report the problem on the module's bug forum. This insures that the module maintainer is aware of the problem and can work on a fix independent of the Drupal security team. After reporting to the module maintainer you may want to publish to a public mailing list such as Full-Disclosure in order to warn other Drupal site maintainers who may be using the module and who would want to know about the problems.