Developing Drupal Module Exploits

30 November -0001

Introduction

Drupal is a wonderful Content Management System (CMS) that comes with a lot of extensible functionality. While the Drupal security team does a great job of making sure the core modules distributed with Drupal are secure, there are a host of third party contributed modules that often contain security problems. In this tutorial I'm going to pick on one module in particular and show you how to deduce security holes based on announcements to the Drupal security list.

Whenever you use an open source product you should always subscribe to the security mailing list for that piece of software. In the case of Drupal you can subscribe to the Drupal security mailing list by following the instructions on their website at http://drupal.org/security. The Drupal security team regularly sends out announcements about security vulnerabilities reported in Drupal modules.

Developing the Exploit

Discovering a security vulnerability involves careful audit of source code or perhaps utilizing some tools to test the strength of a module directly. Reverse engineering an exploit from a security announcement is a much simpler process. The technique most people use is common across almost any platform. First you wait for a security vulnerability announcement, then get a patch. By comparing the software before the patch is applied to the software after the patch is applied you can spot exactly where instructions changed. This highlights the parts of the code that contain the security vulnerability. We'll use this method to spot some problems in a Drupal module, but you can use the same method to develop exploits for any piece of software.

On June 11th, 2008 the Drupal security team sent out advisory SA-2008-032 that announced there was a highly critical remotely exploitable flaw in the Magic Tabs module. In this case, the module developer had discovered the vulnerability and had released a patch by the time the announcement came out. The first step in discovering the flaw was to find the patch and compare it to the original.

The Magic Tabs module consists of eight files:

LICENSE.txt
loading.gif
magic_tabs.css
magic_tabs.info
magic_tabs.module
README
tableft.gif
tabright.gif

We can pretty much rule out the image and text files as not being problematic from the start. This leaves us with a couple of areas to examine. The CSS file is inert, so that's not a problem. The info file is generally used for reporting version and other administrative information, and a quick glance through it shows there is no PHP or other dynamic functionality. This leaves us looking at the magic_tabs.module file for the flaw.

Drupal modules all have a .module file in them that describes the dynamic functionality of the module. If you're not familiar with the Drupal API you're going to have a tough time developing exploits for Drupal vulnerabilities. This is the case with almost any technology though - the better you understand the underlying technology or software, the more easier it will be for you to develop exploits.

Finding the Vulnerability

Once we've identified the .module file, we download the patched version of the file. Next we locate an unpatched version of the same file. The easiest way to find the two versions of the file we want is to browse the CVS repository for the Drupal module at cvs.drupal.org. Once we've downloaded the two versions of the file (the patched and unpatched) we use the unix command "diff" to check the differences between the two. Note that I'm comparing two versions of the same release (5.x-1.1) from CVS, one before the SA and one after (rather than comparing version 5.x-1.1 with version 5.x-1.0):

diff magic_tabs.module\?revision\=1.3.2.4 magic_tabs.module\?revision\=1.3.2.5
2c2
< // $Id: magic_tabs.module,v 1.3.2.3 2008/06/01 12:46:24 yhager Exp $
---
> // $Id: magic_tabs.module,v 1.3.2.4 2008/06/01 15:52:30 yhager Exp $
6a7,8
>   global $_menu;
>
14a17
>     unset($_menu['items']);
17,19c20,22
<       // no access to this URL. Switch back and report.
<       menu_set_active_item($q);
<       watchdog('magic_tabs', t('User !user possibly tried to access %path using ajax call, but has no permission to access this URL', array('!user' => theme('username', $user), '%path' => $_GET['referer'])), WATCHDOG_WARNING);
---
>       // no access to this URL. Return nothing and report.
>       watchdog('magic_tabs', t('User %user possibly tried to access %path using an AJAX call, but has no permission to access this URL.', array('%user' => $user->name, '%path' => $_GET['referer'])), WATCHDOG_WARNING);
>       return;
30c33
<     watchdog('magic_tabs', t('User !user tried to use a callback %callback without it being registered first', array('!user' => theme('username', $user), '%callback' => $callback)), WATCHDOG_WARNING);
---
>     watchdog('magic_tabs', t('User %user tried to use a callback %callback without it being registered first.', array('%user' => $user->name, '%callback' => $callback)), WATCHDOG_WARNING);
34a38
>     unset($_menu['items']);
37c41
<
---
>
60c64
<       $path,
---
>       $path,
64,65c68,69
<           $('#$callback .magic_content').addClass('hidden');
<           $('#$callback .loading').removeClass('hidden');
---
>           $('#$callback .magic_content').addClass('hidden');
>           $('#$callback .loading').removeClass('hidden');
74c78
<
---
>
101a106
>
104c109
<   /**
---
>   /**
147c152
<     'content' => t('Current path is !url', array('!url' => $_GET['q'])),
---
>     'content' => t('Content of the third magic tab'),

Understanding the diff output can be a little difficult. Lines proceeded with the less than symbol appear in the first file, lines with the greater than symbol appear in the second file. You can compare the two versions to see differences. If you spot lines that appear in only one version those are dead giveaways.

Looking through the output we see a lot of innocuous material. It's tough to spot the security flaw right away. After a lot of careful study you can find a few lines of code that eliminate the use of the theme() function. These are:

<       watchdog('magic_tabs', t('User !user possibly tried to access %path using ajax call, but has no permission to access this URL', array('!user' => theme('username', $user), '%path' => $_GET['referer'])), WATCHDOG_WARNING);
---
>       // no access to this URL. Return nothing and report.
>       watchdog('magic_tabs', t('User %user possibly tried to access %path using an AJAX call, but has no permission to access this URL.', array('%user' => $user->name, '%path' => $_GET['referer'])), WATCHDOG_WARNING);
>       return;
30c33
<     watchdog('magic_tabs', t('User !user tried to use a callback %callback without it being registered first', array('!user' => theme('username', $user), '%callback' => $callback)), WATCHDOG_WARNING);
---
>     watchdog('magic_tabs', t('User %user tried to use a callback %callback without it being registered first.', array('%user' => $user->name, '%callback' => $callback)), WATCHDOG_WARNING);

The watchdog() function in Drupal is used to report errors and displays these reports in the administrative interface under 'Recent Log Entries'. You'll notice that the patch fixed the reporting of the username and referer, this is our first clue. Unfortunately username isn't a malleable variable, but refer certainly is, it's a GET variable that we can change quite easily. If we look carefully at the code then we can guess that by manipulating this variable we might be able to make some changes.

Looking at the theme() function in the Drupal includes/theme.inc we find:

function theme() {
  static $functions;
  $args = func_get_args();
  $function = array_shift($args);

  if (!isset($functions[$function])) {
    $functions[$function] = theme_get_function($function);
  }
  if ($functions[$function]) {
    return call_user_func_array($functions[$function], $args);
  }
}

This is probably a good function to exploit if it's used improperly. It looks like if we could change the value of $user then we might be able to overload the number of arguments going to the theme() function and perhaps trigger other arbitrary function calls via Drupal.

Finishing the Exploit

For now lets stick with the easy target though, the GET variable referer. If we look at the code we'll notice that this function is only going to get called if the $_ajax variable is set in the function call:

if ($_ajax) {
    menu_set_active_item($_GET['referer']);
    if (!_menu_item_is_accessible(menu_get_active_item())) {
      // no access to this URL. Switch back and report.
      menu_set_active_item($q);
      watchdog('magic_tabs', t('User !user possibly tried to access "!path" using ajax call, but has no permission to access this URL', array('!user' => theme('username', $user), '!path' => $_GET['referer'])), WATCHDOG_WARNING);
    }
  }
  

This means the PHP code snippet that includes the call magic_tabs_get() to display the magic tabs must include the optional third parameter and have it set to TRUE. For instance, if we install the magic_tabs module version 5.x-1.0 and create some new content using the PHP input type and include the following code to display the example tabs:

  <?php print magic_tabs_get('magic_tabs_example_callback','first',TRUE); ?>
  

We can introduce the vulnerability. When we call up the page that includes this content, if we change the URL so that it includes some JavaScript we can easily trigger an XSS attack. For instance, if our Drupal instance is installed at http://1982.168.0.2/drupal and after setting up magic tabs using the PHP above and we call the URL ?magic_tabs_example_callback_tab=1&referer=<script>alert('foo');</script> we won't see anything interesting. However, when an administrator logs in and then checks the logs they'll see an alert under recent log entries:

Administrator examining the Drupal log report

Next, when the click to check the log entry you'll see the JavaScript is interpreted.

Drupal XSS attack in action

The advisory for magic_tabs actually mentions remote exploitation of arbitrary PHP code, but as you can see we've already been able to work back from the advisory and patch to find a vulnerability in the code. You can use this same technique to find vulnerabilities in almost any software. Of course, your mileage may vary depending on the complexity of the code. Some patches may include a lot of extra material so that finding the actual vulnerability may be extremely difficult to work out.