mcdruid.co.uk: A persistent Drupal 7 exploit using a pluggable variable

A couple of years ago I was asked to take a look at a Drupal 7 site that was performing poorly where a colleague had spotted a strange function call in an Application Performance Management (APM) system.

The APM traces we were looking at included a __lamda_func under which was a class called Ratel. Under those were some apparent external calls to some dodgy looking domains.

One of my very excellent colleagues had done some digging and found some more details about the domains which confirmed their apparent dodginess.

They had also come across a github gist which looked relevant – it had the PHP source code for a Ratel class which appears to be an SEO spam injection tool:

https://gist.github.com/isholgueras/b373c73fa1fba1e604124d48a7559436

This gist included encoded versions of the dodgy URLs we’d seen when trying to analyse what was slowing the site down.

However it wasn’t immediately obvious how this code was running within the infected Drupal site.

We’d grepped the file system and not found any signs of this compromise. One trick that’s sometimes useful is to search a recent database dump.

Doing so turned up a reference to the Ratel class within the cache tables, but when we took a closer look inside the cache there wasn’t much more info to go on:

$ drush ev 'print_r(cache_get("lookup_cache", "cache_bootstrap"));' stdClass Object (     [cid] => lookup_cache     [data] => Array         (   [...snip...]               [cRatel] =>              [iRatel] =>              [tRatel] => 

So this was more evidence that the malicious code had been injected into Drupal, but didn’t tell us how.

I took a closer look at the malicious source code and noticed something it was doing to try and hide from logged in users:

  if (function_exists('is_user_logged_in')) {     if (is_user_logged_in()) {       return FALSE;     }   }

Being so used to reading Drupal code, I think I’d initially thought this was a Drupal API call.

However, on closer inspection I realised it’s actually a very similarly named WordPress function.

That meant that the function almost certainly would not exist in this Drupal site, and that gave me a way to hook into the malicious code and find out more about how it had got into this site.

I temporarily added a definition for this function to the site’s settings.php within which I output some backtrace information to a static file – something like this:

function is_user_logged_in() {   $debug = debug_backtrace();   file_put_contents('/tmp/debug.txt', print_r($debug, TRUE), FILE_APPEND);   return FALSE; }

This quickly yielded some useful info – along the lines of:

$ cat debug.txt  Array (     [0] => Array         (             [file] => /path/to/drupal/sites/default/files/a.jpg(9) : runtime-created function             [line] => 1             [function] => is_user_logged_in             [args] => Array                 (                 )           )       [1] => Array         (             [file] => /path/to/drupal/sites/default/files/a.jpg             [line] => 10             [function] => __lambda_func             [args] => Array                 (                 )           )       [2] => Array         (             [file] => /path/to/drupal/includes/bootstrap.inc             [line] => 2524             [args] => Array                 (                     [0] => /path/to/drupal/sites/default/files/a.jpg                 )               [function] => require_once         )

Wow, so it looked like the malicious code was hiding inside a fake jpg file in the site’s files directory.

Having a look at the fake image, it did indeed contain a copy of the code we’d been looking at in the gist, albeit one that was further wrapped in obfuscation.

$ file sites/default/files/a.jpg     sites/default/files/a.jpg: PHP script, ASCII text, with very long lines, with CRLF line terminators

The malicious Ratel code had been encoded and serialized, and the fake image file was turning that obfuscated string back into executable code and creating a dynamic function from it:

$serialized = '** LONG STRING OF OBFUSCATED CODE **'; $rawData = array_map("base64_decode", unserialize($serialized)); $rawData = implode($rawData); $outputData = create_function(false, $rawData); call_user_func($outputData);

That’s where the lamda function we’d been seeing had come from.

The final piece of the puzzle was how this fake image file was actually being executed during the Drupal bootstrap.

The backtrace we’d extracted gave us the answer; the require_once call on line 2524 of bootstrap.inc was this:

2523         case DRUPAL_BOOTSTRAP_SESSION: 2524           require_once DRUPAL_ROOT . '/' . variable_get('session_inc', 'includes/session.inc'); 2525           drupal_session_initialize(); 2526           break;

So the attacker had managed to inject the path to their fake image into the session_inc Drupal variable.

This was further confirmed by the fact that the malicious code in the fake image actually included the real Drupal session code itself, so as not to interfere with Drupal’s normal operation.

require_once('includes/session.inc');

So although the Ratel class had perhaps initially been put together with WordPress in mind, the attacker had tailored the exploit very specifically to Drupal 7.

Drupal has a mechanism to disallow uploaded files from being executed as PHP but that didn’t help in this case as the code was being included from within Drupal itself.

At some point there must have been something like a Remote Code Execution or SQL Injection vulnerability on this site which allowed the attacker to inject their variable into the database.

It’s possible that was one of the notorious Drupal vulnerabilities often referred to as Drupalgeddon 1 and 2, but we don’t know for sure. We believe that the site was most likely infected while at a previous host.

This technique doesn’t represent a vulnerability in itself, as the attacker needed to be able to upload the fake image and (most importantly) inject their malicious variable into the site.

It was, however, quite an interesting technique for achieving persistence within the Drupal site.

Once we’d uncovered all of these details, cleaning up the infection was as simple as deleting the injected variable and removing the malicious fake image file.

What could the site have done to defend itself against this attack?

Well the injection of variable was mostly likely done via an exploit of an unpatched vulnerability on the site. Keeping up-to-date with patches from the Drupal Security Team is always advisable.

Other than that, something like the mimedetect module might have been able to prevent the upload of the fake image file. Note that newer versions of Drupal have this capability built-in.

A manual review of the variables in the site’s database could have caught this; there are a handful of variables that provide “pluggability” in D7 but session_inc is probably one of the most attractive from an attacker’s point of view as it’s typically invoked on most bootstraps unlike some of the others:

drupal-7.x$ grep -orh "variable_get.*.inc')" includes modules | sort | uniq   variable_get('lock_inc', 'includes/lock.inc') variable_get('menu_inc', 'includes/menu.inc') variable_get('password_inc', 'includes/password.inc') variable_get('path_inc', 'includes/path.inc') variable_get('session_inc', 'includes/session.inc')

A simple drush command can show whether any of these variables are set:

$ drush vget _inc No matching variable found.

Once we knew what had happened to the site we found a couple of references online to similar exploits:

This article was republished from its original source.
Call Us: 1(800)730-2416

Pixeldust is a 20-year-old web development agency specializing in Drupal and WordPress and working with clients all over the country. With our best in class capabilities, we work with small businesses and fortune 500 companies alike. Give us a call at 1(800)730-2416 and let’s talk about your project.

FREE Drupal SEO Audit

Test your site below to see which issues need to be fixed. We will fix them and optimize your Drupal site 100% for Google and Bing. (Allow 30-60 seconds to gather data.)

Powered by

mcdruid.co.uk: A persistent Drupal 7 exploit using a pluggable variable

On-Site Drupal SEO Master Setup

We make sure your site is 100% optimized (and stays that way) for the best SEO results.

With Pixeldust On-site (or On-page) SEO we make changes to your site’s structure and performance to make it easier for search engines to see and understand your site’s content. Search engines use algorithms to rank sites by degrees of relevance. Our on-site optimization ensures your site is configured to provide information in a way that meets Google and Bing standards for optimal indexing.

This service includes:

  • Pathauto install and configuration for SEO-friendly URLs.
  • Meta Tags install and configuration with dynamic tokens for meta titles and descriptions for all content types.
  • Install and fix all issues on the SEO checklist module.
  • Install and configure XML sitemap module and submit sitemaps.
  • Install and configure Google Analytics Module.
  • Install and configure Yoast.
  • Install and configure the Advanced Aggregation module to improve performance by minifying and merging CSS and JS.
  • Install and configure Schema.org Metatag.
  • Configure robots.txt.
  • Google Search Console setup snd configuration.
  • Find & Fix H1 tags.
  • Find and fix duplicate/missing meta descriptions.
  • Find and fix duplicate title tags.
  • Improve title, meta tags, and site descriptions.
  • Optimize images for better search engine optimization. Automate where possible.
  • Find and fix the missing alt and title tag for all images. Automate where possible.
  • The project takes 1 week to complete.