Auditing Drupal Modules for XSS Vulnerabilities

25 August 2011
Cross site scripting (XSS) vulnerabilities can have an incredibly damaging effect on a Drupal site. In addition to introducing malware, redirecting users, or other nefarious interactions, arbitrary script on a Drupal site can actually interact with the system allowing an XSS vulnerability to escalate into an account compromise or even a site compromise. Finding XSS vulnerabilities in Drupal modules can be a tricky task, but there are some general guidelines you can follow to ease the task.

About XSS

Cross site scripting (XSS) is a pervasive problem in Drupal because the development team takes the approach that data should be sanitized upon display rather than input. The rational for this decision is to maintain data integrity despite translation or manipulation. This is a somewhat non-standard approach in web application circles and leads to no small amount of confusion about "trusted" data sources and the display of data. In general, all user supplied data must be filtered upon display. Drupal provides several useful API calls to facilitate this transformation. These include, but are not limited to, filter_xss(), check_plain(), and the t() function. Drupal output sanitization functions must be used carefully and properly, however, especially the t() function as misuse can introduce unexpected vulnerabilities.

Impact

Cross site scripting can have an incredibly damaging effect on a Drupal site. This is mainly due to the fact that a script can utilize AJAX and other functions to create a same domain request forgery. This is distinct from a cross site request forgery (XSRF) in that the user is actually logged into and browsing the domain. Because the Drupal user administration doesn't require re-entry of a password in order to implement changes, any JavaScript on a Drupal site can silently update a user's password if they request the JavaScript. This means that in addition to introducing malware, redirecting users, or other nefarious interactions, arbitrary script on a Drupal site can actually interact with the system allowing an XSS vulnerability to escalate into an account compromise or even a site compromise.

Types of XSS

There are generally two classifications of cross site scripting, stored and reflected. A stored cross site scripting vulnerability is one that persists on the site (usually in the database) so that every user requesting a page will be exposed to the attack. A reflected cross site scripting vulnerability is one that requires input from the requestor in order to induce the vulnerability. In a reflected XSS malicious users usually induce victims to click on or otherwise follow links that contain additional URL parameters. This type of attack can also be used against web crawlers, otherwise known as spiders. This is usually done to inject HTML links into page display if the crawler follows a certain link, thus causing the crawler to evaluate your Drupal site as though it contained a link to another site, usually to boost the other site's ranking or search relevance.

Examples

When reviewing code for XSS vulnerabilities you want to be sure to check for any occurrence of user supplied data that is being rendered. This not only includes obvious user supplied data such as form data or URL variables, but also less apparent user supplied data such as browser referer values, node titles, and usernames. Any data that users can maniuplate could become a target for script injection. It also bears mentioning that injections do not necessarily have to include the "" tags. An output that allowed for unfiltered display of HTML tag elements could allow an attacker to inject an onClick or other JavaScript function into a tag element unless proper display filtering is enforced.

Examining two Drupal modules that contain publicized XSS vulnerabilities illustrates the vulnerability explicitly. For more information about reverse engineering vulnerabilities in Drupal modules based on security announcements and updates see http://madirish.net/?article=212. The first is the Menu Block module XSS vulnerability (http://drupal.org/node/752236). Viewing the revision history at http://drupalcode.org/viewvc/drupal/contributions/modules/menu_block/men... you can clearly see the addition of the check_plain() function used to sanitize the output of menu item title's on line 91. This is an example of a stored cross site scripting flaw. In order to exploit this vulnerability an attacker must have the privileges to create menu items in order to inject a script into the title element. This privilege could be gained by compromising a Drupal user account or even by gaining write access to the menu table in the Drupal database.

Reflected XSS are much less common in Drupal simply because Drupal utilizes URL's to construct the actual page response. In essence there is no page but the index page, and all other subdirectory specifications in the URL are merely translated into URL parameters. The eTracker module (http://drupal.org/project/eTracker), however, had a reflected cross site scripting flaw (http://drupal.org/node/731682). The module displayed a piece of the URL in the actual HTML output of the code. This could cause arbitrary script to be displayed to anyone who followed a link that included malicious code. Examining the diff of the update (http://drupalcode.org/viewvc/drupal/contributions/modules/etracker/etrac...) you can spot the change on line 156 that escaptes the input for the $_GET['q'] parameter using the check_plain() function.

Finding XSS Vulnerabilities

Identifying XSS in Drupal modules can be tricky. It is important that you perform testing on a platform dedicated for the purpose. The reason for this is that many XSS are input in some modules (including core) and are mishandled in other modules. So when searching for XSS you have to examine the entire Drupal installation, not just specific modules.

Testing for XSS can actually be quite simple. One easy way is to manually fill Drupal form fields and URL's with script tags. It usually helps to use a tag like "alert('field description');" so that when alerts appear you can easily spot the offending form field. Of course, once an input is identified you have to move toward tracking down the source. Usually searching the source code for the help text or other display around the form field is useful. Drupal uses the form api to compose many of the forms so searching for specific display text is the key to finding the code that invokes a specific form field. Of course, most XSS actually manifests during display, so you should look outside of the form that actually allows for input of the data to the places where the XSS is rendered. Sometimes this can take a little digging. Additionally, there are some modules that allow for input of data but handle sanitization perfectly well and it is only with the addition of other modules or themes that problems creep up. This is especially true of XSS that you might find in a complex module such as Views, or Bibliography. This process can be quite time consuming, but extremely accurate.

The Tag Order module is a great example of a module that requires a complex interaction of inputs to trigger a cross site scripting vulnerability as described in http://drupal.org/node/745386. By creating a new taxonomy with the name "alert('xss');" from the Administer -> Content management -> Taxonomy screen you exploit a stored cross site scripting vulnerability. This condition is triggered when someone goes to the administration screen for the Tag Order module at Administer -> Site configuration -> Tag Order Settings. Looking at this screen makes the vulnerability obvious, but finding it in code is a bit trickier. The simplest way is to search through the module code for the string "The entry order for these vocabularies should be remembered." We find this string in line 37 in the function:

function tagorder_admin_settings() {
  $vocabs = taxonomy_get_vocabularies();
  $vocabulary = array();
  foreach ($vocabs AS $vocab) {
    $vocabulary[$vocab->vid] = $vocab->name;
  }
  $form['tagorder_vocab'] = array(
    '#type' => 'checkboxes',
    '#title' => t('The entry order for these vocabularies should be remembered.'),
    '#options' => $vocabulary,
    '#default_value' => variable_get('tagorder_vocab', array('tags')),
  );
  $form['array_filter'] = array('#type' => 'hidden');
  return system_settings_form($form);
}

You can easily see that the $vocabulary variable isn't being sanitized on output. Simply surrounding the $vocab->name in a check_plain() mitigates this vulnerability. Referring to the module CVS documentation at http://drupalcode.org/viewvc/drupal/contributions/modules/tagorder/tagor... you can clearly see that this is exactly what was done to fix this vulnerability.

Reporting Findings

Once you've identified an XSS 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.