The Linux Page

Security Fix for Drupal 6 — CVE-2018-7600 — SA-CORE-2018-002

A meteorite flying in Earth atmosphere, ready to smash the ground and kill 95% of the animals on the entire planet.

As per the following page:

https://www.drupal.org/SA-CORE-2018-002

Drupal 6.x is also at risk from this issue. You can download Drupal 7 or 8 and compare and find out what is different between both versions.

The only difference between 7.57 and 7.58, which makes a difference, is a class used to remove any parameter that starts with a hash (#). Drupal defines arrays with keys. To distinguish between regular values and keys, Drupal uses words that start with a hash (#) for those keys.

Here are two lines of code that show that '#...' key usage:

$form['#pre_render'][] = 'content_alter_extra_weights';
$form['#content_extra_fields'] = $type['extra'];

So, to avoid problems, Drupal decided that all tinted variables needed to be checked for input with keys and remove all those keys. What is not clear is where/how one could test whether your website is hackable or not.

The following is my fix for Drupal 6.x, I hope you appreciate the fix!

/**
 * Unsets all disallowed global variables. See $allowed for what's allowed.
 */
function drupal_unset_globals()
{
  if(ini_get('register_globals'))
  {
    $allowed = array('_ENV' => 1, '_GET' => 1, '_POST' => 1, '_COOKIE' => 1, '_FILES' => 1, '_SERVER' => 1, '_REQUEST' => 1, 'GLOBALS' => 1);
    foreach($GLOBALS as $key => $value)
    {
      if(!isset($allowed[$key]))
      {
        unset($GLOBALS[$key]);
      }
      else
      {
        strip_dangerous_values($GLOBALS[$key]);
      }
    }
  }
}

function strip_dangerous_values($input)
{
  if(is_array($input))
  {
    foreach($input as $key => $value)
    {
      if($key !== '' && $key[0] === '#')
      {
        unset($input[$key]);
      }
      else
      {
        // recursive in case the sub-content is an array
        //
        $input[$key] = strip_dangerous_values($input[$key]);
      }
    }
  }
  return $input;
}

Search your bootstrap.inc file.

The drupal_unset_globals() function exists in your bootstrap.inc file. However, the strip_dangerous_values() is a new function. That's the one which does the dirty job of removing unwanted keys.

Note that I simplified the code. The Drupal 7 version (see below) includes a whitelist and it registers the fields that get removed to save them in the logs. I don't think that login all such attempts is a good idea. It could end up being a problem (i.e. filling up your logs with useless data as a hacker makes many attempts and all that gets in your logs.)

If you have any questions, feel free to post a comment or contact me on m2osw.com/contact.

The Drupal 7.58 version has this configuration function modified. It has 2 new lines at the bottom calling a sanitize function.

/**
 * Sets up the script environment and loads settings.php.
 */
function _drupal_bootstrap_configuration() {
  // Set the Drupal custom error handler.
  set_error_handler('_drupal_error_handler');
  set_exception_handler('_drupal_exception_handler');

  drupal_environment_initialize();
  // Start a page timer:
  timer_start('page');
  // Initialize the configuration, including variables from settings.php.
  drupal_settings_initialize();

  // Sanitize unsafe keys from the request.
  require_once DRUPAL_ROOT . '/includes/request-sanitizer.inc';
  DrupalRequestSanitizer::sanitize();
}

Notice how the newer version of Drupal make use of more objects than Drupal 6.

Now, the sanitizer is a rather lengthy class. It includes a way to run the sanitation only once. It specifically checks the $_GET, $_POST, $_COOKIE, and the $_REQUEST global variables.

My fix for Drupal 6 above will check ALL global variables at the time that function gets called. Although really we delete many global variables too.

<?php

/**
 * @file
 * Contains code for sanitizing user input from the request.
 */

/**
 * Sanitizes user input from the request.
 */
class DrupalRequestSanitizer {

  /**
   * Tracks whether the request was already sanitized.
   */
  protected static $sanitized = FALSE;

  /**
   * Modifies the request to strip dangerous keys from user input.
   */
  public static function sanitize() {
    if (!self::$sanitized) {
      $whitelist = variable_get('sanitize_input_whitelist', array());
      $log_sanitized_keys = variable_get('sanitize_input_logging', FALSE);

      // Process query string parameters.
      $get_sanitized_keys = array();
      $_GET = self::stripDangerousValues($_GET, $whitelist, $get_sanitized_keys);
      if ($log_sanitized_keys && $get_sanitized_keys) {
        _drupal_trigger_error_with_delayed_logging(format_string('Potentially unsafe keys removed from query string parameters (GET): @keys', array('@keys' => implode(', ', $get_sanitized_keys))), E_USER_NOTICE);
      }

      // Process request body parameters.
      $post_sanitized_keys = array();
      $_POST = self::stripDangerousValues($_POST, $whitelist, $post_sanitized_keys);
      if ($log_sanitized_keys && $post_sanitized_keys) {
        _drupal_trigger_error_with_delayed_logging(format_string('Potentially unsafe keys removed from request body parameters (POST): @keys', array('@keys' => implode(', ', $post_sanitized_keys))), E_USER_NOTICE);
      }

      // Process cookie parameters.
      $cookie_sanitized_keys = array();
      $_COOKIE = self::stripDangerousValues($_COOKIE, $whitelist, $cookie_sanitized_keys);
      if ($log_sanitized_keys && $cookie_sanitized_keys) {
        _drupal_trigger_error_with_delayed_logging(format_string('Potentially unsafe keys removed from cookie parameters (COOKIE): @keys', array('@keys' => implode(', ', $cookie_sanitized_keys))), E_USER_NOTICE);
      }

      $request_sanitized_keys = array();
      $_REQUEST = self::stripDangerousValues($_REQUEST, $whitelist, $request_sanitized_keys);

      self::$sanitized = TRUE;
    }
  }

  /**
   * Strips dangerous keys from the provided input.
   *
   * @param mixed $input
   *   The input to sanitize.
   * @param string[] $whitelist
   *   An array of keys to whitelist as safe.
   * @param string[] $sanitized_keys
   *   An array of keys that have been removed.
   *
   * @return mixed
   *   The sanitized input.
   */
  protected static function stripDangerousValues($input, array $whitelist, array &$sanitized_keys) {
    if (is_array($input)) {
      foreach ($input as $key => $value) {
        if ($key !== '' && $key[0] === '#' && !in_array($key, $whitelist, TRUE)) {
          unset($input[$key]);
          $sanitized_keys[] = $key;
        }
        else {
          $input[$key] = self::stripDangerousValues($input[$key], $whitelist, $sanitized_keys);
        }
      }
    }
    return $input;
  }

}

As I mentioned, I'm not too sure how the '#key' syntax can have an effect with any of the existing code, but I would imagine that if they put that in, it's important enough. After all, the second name of this bug is Drupalgeddon.

Hopefully your Drupal 6.x websites have not been hacked yet!

Note: The following patch in the Drupalgeddon series, SA-CORE-2018-004, is very similar but pertain to dropping the destination=... query string if it includes a '#' character. I again don't see the point, also it should have no effect on the existing code with the patch above since once you get redirected using that destination, you get the URL checked closely before things move forward. In other words, I would not worry about this one. Note that for 7.x, there were two other fixes which do not apply to 6.x (i.e. it's new code that is not available in 6.x to start with.)