A common trend we see is that bad actors will upload malicious plugins to WordPress sites. These plugins serve a wide variety of functions from injecting spam to redirecting sites to other malicious content. In this article we will examine a more dangerous method where plugins can be used to steal admin credentials.
Identifying the malware
During a routine malware scan we noticed a plugin labeled wp-runtime-cache in the wp-content/plugins directory. Seems innocent enough, right? After all, just about every site has at least one layer of cache to help with performance. When caching plugins are installed, you’ll usually find some menu to manage that at the top of the wp-admin panel or there will be some option under Settings on the left menu when logged in.
We did not see any options for clearing cache and the plugin didn’t show up in the installed plugins list inside of wp-admin. It’s not uncommon for attackers to hide malicious plugins so this definitely warranted a further investigation.
Taking a look at the wp-runtime-cache directory, we could see one file: wp-runtime-cache.php. That’s also quite unusual since any valid plugin is going to utilize additional PHP and JS files for functionality. I mentioned we have seen a trend of utilizing malicious plugins, and in many cases those malicious plugin directories only include one file containing the malicious code.
Let’s take a look at the plugin.
Right off the bat we can see a few red flags.
- The plugin description, author and URL are empty. In any valid plugin the vendor will identify themselves and will usually provide the plugin source URL or at least a location to a support page.
- We see some base64 content, a trademark of malware
- The plugin is utilizing random variable names, like woocomHeic0971 and pbes2PITR0339. We also see an interesting variable: infiltrateDocumentStore0460. I can’t think of any single legitimate case where a valid plugin would want to secretly infiltrate any content.
Just a bit of clarification here: it’s not extremely uncommon for software vendors to use base64 obfuscation in premium plugins and themes. Typically, however, either the majority of the code will be obfuscated or they will obfuscate a specific line containing a license key and the line containing that obfuscation will be labeled as such.
Working through the plugin
Because this malware is running as a plugin it is executed every time the site loads on any page, including wp-admin. The first line of code instructs the plugin to run a set of tasks in the octopusJson50286 function whenever someone logs in to wp-admin.
add_action(‘wp_login’, ‘octopusJson50286’, 10, 2);
That function accepts two variables, woocomHeic0971 and ntpExcerpt0821.
function octopusJson50286($woocomHeic0971, $ntpExcerpt0821)
Let’s work out what values those variables are storing. Further down the malicious function, we can see it is building an array of data from the user login details.
Here we can see the first variable passed to the function – woocomHeic0971 – when it is called at login stores the submitted username. The second variable, ntpExcerpt0821, is defined as an object to contain an array of predefined user capabilities or roles. We also see a variable defining very specific roles to be checked for.
Decoding those two base64 values, we come up with the following.
- bWFuYWdlX29wdGlvbnM=: manage_options, an admin level role
- ZWRpdF9wYWdlcw==: edit_pages, an editor level role
We then encounter a loop to compare the currently logged in user’s capabilities against those predefined roles.
If the current user’s capabilities match one of those roles, the phpstanTablespace0600 variable is set to true. Once the loop is completed, the plugin checks the value of the phpstanTablespace0600 and if that is set to false, meaning that the current user is not an admin or editor, the plugin exits and performs no further actions.
However, if the user did match one of the roles the plugin is allowed to continue on to the next exfiltration step.
As mentioned earlier, the code defines an array of user details like the username, password, and capabilities or roles. That data is then sent to a URL defined in yet another base64 encoded value
aHR0cHM6Ly93b29jb21tZXJjZS1jaGVjay5jb20vcmVwb3J0LXRv
via the wp_remote_post function call. If we head over to the WordPress developer guides we see that wp_remote_post is a built-in WordPress function which allows an array of data to be sent to an external location.
Decoding the above value, we have our target: https[:]//woocommerce-check[.]com/report-to
One of the goals of bad actors is to hide their malicious attempts and the plugin provides additional functionality for that. Back at the beginning of the plugin code, we saw a second function call.
add_action(‘pre_current_active_plugins’, ‘pbes2PITR0339’);
We can find this function at the bottom of the plugin.
The function retrieves the currently logged in username and then calls yet another function, detachConcurrency0788, which generates a hash value based on the current username and the site URL.
Inside the detachConcurrency0788 we see a very specific value, WsXZjIFxgnLnC5V, which is probably a pre-generated hash value for some specific malicious user the attackers used to inject the site to begin with.
If the current user’s hash value matches the hash value generated in the function, then the code knows that a malicious user is logged in and exits the function, there is no need to hide the plugin from view. Otherwise, the code continues on to hide the plugin from the wp-admin plugins list.
Let’s take a look at the exfiltration domain we identified earlier.
Domain: woocommerce-check[.]com
Registered On: 2024-10-27
Expires On: 2025-10-27
Updated On: 2024-10-28
State: AR
Country: US
Abuse Phone: +852.68584411
We see that the domain was registered in recent months. It’s quite common for attackers to register new domains to skirt around detection, and to continue their attacks when previous domains are shut down or flagged as malicious by any of the major vendors like Google. We can also see a very interesting discrepancy in the registration details: the domain appears to be registered out of the state of Arkansas, but the phone number appears to use a country code 852, which is located in Hong Kong.
Wrapping Up
As demonstrated here, once an attacker has gained access to a site it can be quite easy to hide their malicious activities. In this case, the attackers were able to obtain wp-admin login details and send that data over to an external server so that they could login later.
This attack highlights the importance of auditing your site’s plugins and users, and maintaining updated admin passwords. While the plugin was hidden, a regular security audit using tools like our server-side scanner or the Sucuri WordPress plugin would have provided notifications that new files were uploaded to the site.
A second layer of protection, like 2FA or even IP restrictions on the WordPress login page, could block any attempts to login there even if the attackers obtained a set of passwords.
One additional step that should be taken after a compromise, especially where admin credentials were stolen, is to update the WordPress salts in wp-config.php. The ‘salts’ are a set of random strings that help to encrypt login details and can be easily generated by heading over to the WordPress.org Salt Generator. Without changing those, the attackers could pass any hashed passwords through a generator to extract the passwords in plain text.
If you believe your site may have been hacked we’re always here to take a look.