Open source software security

Drupal SuperCron 6.x-1.3 XSS Vulnerability

12 January 2012

Description of Vulnerability

Drupal (http://drupal.org) is a robust content management system (CMS) written in PHP and MySQL. The Drupal SuperCron module (https://drupal.org/project/supercron), created by 63 Reasons (http://www.63reasons.com/), "is a complete replacement for Drupal's built-in Cron functionality." The SuperCron module contains a persistent arbitrary HTML injection vulnerability (also known as cross site scripting, or XSS) due to the fact that it fails to user supplied data before display.

Systems affected:

Drupal 6.22 with SuperCron 6.x-1.3 was tested and shown to be vulnerable

Impact

Attackers could inject arbitrary scripts into pages affecting site users. This could result in administrative account compromise leading to web server process compromise. A more likely scenario would be for an attacker to inject hidden content (such as iframes, applets, or embedded objects) that would attack client browsers in an attempt to compromise site users' machines. This vulnerability could also be used to launch cross site request forgery (XSRF) attacks against the site that could have other unexpected consequences.

Mitigating factors:

In order to exploit this vulnerability the attacker must have permissions to access administration pages. This is an extremely loose permission given the impact not only of this vulnerability but also of the module's general functionality.

Proof of concept:

  1. Install Drupal 6-22 and install and enable SuperCron 6.x-1.3
  2. Navigate to SuperCron's Firewall settings at ?q=admin/settings/supercron/firewall
  3. Enable the firewall
  4. Expand the 'Insert IP' portion of the form
  5. Insert "<script>alert('xss');</script>" for the IP address and click the "Insert" button
  6. Observe the transient JavaScript in the Drupal confirmation message
  7. Persistent JavaScript is rendered at ?q=admin/settings/supercron/firewall

Patch:

Applying the following patch mitigates this issue in version 6.x-1.3 (and adds new permissions for the module)

--- supercron/supercron.module	2009-06-15 02:55:20.000000000 -0400
+++ supercron.fixed/supercron.module	2011-10-30 00:34:24.316054217 -0400
@@ -23,7 +23,7 @@ function supercron_menu() {
     'description' => t('Configure how Cron behaves, which cron hooks are called and in what order, capture debugging information and parallelize tasks.'),    
     'page callback' => 'drupal_get_form',
     'page arguments' => array('supercron_settings'),
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('administer SuperCron'),
     );
   $items['admin/settings/supercron/'] = array(
     'title' => t('Settings'),
@@ -34,20 +34,20 @@ function supercron_menu() {
     'title' => t('Firewall'),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('supercron_firewall_form'),
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('administer SuperCron'),
     'type' => MENU_LOCAL_TASK,
     'parent' => 'admin/settings/supercron'
     );
   $items['admin/settings/supercron/firewall/delete'] = array(
     'page callback' => 'supercron_firewall_delete',
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('administer SuperCron'),
     'type' => MENU_CALLBACK
     );
   $items['admin/settings/supercron/output/%'] = array(
     'title' => t('Cron output in module: '),
     'page callback' => 'supercron_output',
     'page arguments' => array(4),
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('administer SuperCron'),
     'type' => MENU_CALLBACK,
     );
   
@@ -55,7 +55,7 @@ function supercron_menu() {
     'title' => t('Cron exceptions in module: '),
     'page callback' => 'supercron_exception',
     'page arguments' => array(4),
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('administer SuperCron'),
     'type' => MENU_CALLBACK,
     );
   
@@ -63,20 +63,20 @@ function supercron_menu() {
     'title' => t('Call individual cron handlers'),
     'page callback' => 'supercron_invoke_one',
     'page arguments' => array(4, 5),
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('administer SuperCron'),
     'type' => MENU_CALLBACK,
     );
   
   $items[SUPERCRON_INVOKE_ALL] = array(
     'page callback' => 'supercron_drupal_cron_run',
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('administer SuperCron'),
     'type' => MENU_CALLBACK,
     'file' => 'cron.inc',
     );
   
   $items['admin/reports/status/run-cron'] = array(
     'page callback' => 'supercron_drupal_cron_run',
-    'access arguments' => array('access administration pages'),
+    'access arguments' => array('administer SuperCron'),
     'type' => MENU_CALLBACK,
     'file' => 'cron.inc',
     );
@@ -137,6 +137,12 @@ function supercron_firewall_delete($id) 
   drupal_goto('admin/settings/supercron/firewall');
 }
 
+function supercron_firewall_form_validate($form, &$form_state) {
+	if (! preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $form_state['values']['ip']) > 0) {
+		form_set_error('', t('Invalid IP address.'));
+	} 
+}
+
 function supercron_firewall_form(&$form_state) {
   $firewall_enabled = variable_get(SUPERCRON_FIREWALL_ENABLED_VARIABLE, FALSE);
   $form['firewall_enable_field'] = array(
@@ -206,11 +212,11 @@ function supercron_firewall_form(&$form_
     $result = db_query('SELECT * FROM {supercron_ips}');
     while ($ip = db_fetch_object($result)) {
       if ($firewall_enabled) {
-        $form[$ip->iid]['title'] = array('#value' => $ip->ip);
+        $form[$ip->iid]['title'] = array('#value' => check_plain($ip->ip));
         $form[$ip->iid]['delete'] = array('#value' => l(t('delete'), "admin/settings/supercron/firewall/delete/" . $ip->iid));
       }
       else {
-        $form[$ip->iid]['title'] = array('#value' => $ip->ip);
+        $form[$ip->iid]['title'] = array('#value' => check_plain($ip->ip));
         $form[$ip->iid]['delete'] = array('#value' => '<span style="text-decoration: line-through;">' . t('delete') . '</span>');
       }
     }
@@ -220,8 +226,10 @@ function supercron_firewall_form(&$form_
 }
 
 function supercron_firewall_add() {
-  db_query("INSERT INTO {supercron_ips} (ip) VALUES ('%s')", $_POST['ip']);
-  drupal_set_message("IP $_POST[ip] added.");
+	if (preg_match('/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/', $_POST['ip']) > 0) {
+	  db_query("INSERT INTO {supercron_ips} (ip) VALUES ('%s')", $_POST['ip']);
+	  drupal_set_message("IP $_POST[ip] added.");
+	}
 }
 
 function supercron_firewall_enable() {
@@ -457,7 +465,7 @@ function supercron_module_add_output($mo
 function supercron_output($id) {
   $module = db_fetch_array(db_query_range("SELECT * FROM {supercron_enabled_mods} WHERE id=%d", (int)$id, 0, 1));
   drupal_set_title(drupal_get_title() . $module["module_name"]);
-  $output = $module["output"];
+  $output = filter_admin_xss($module["output"]);
   return $output;
 }