WordPress code injections and my emergency recovery toolbox

Anyone who has had a long enough conversation with me about my work knows that as someone who offers security and quality auditing services for WordPress sites, it tends to feel like being the only dentist in a town where nobody brushes their teeth or has their bi-annual check-up.

For many digital agencies and site builders out there, pre-emptive security — which is something that I would be happier to do than emergency disaster recovery — is hardly even an afterthought and using the same dentist analogy, I end up pulling more figurative teeth every year than I’d be happy to admit.

WordPress sites experience security breaches all the time, sometimes they go under our noses, as a malicious actor gets your site’s administrative password and steals your customer database for the past 5 years and your list of WooCommerce sales or hijacks your login credentials to use them elsewhere.

Sometimes your whole site gets hijacked or ends up as good as ruined.

It is the small business owners who are the biggest target here, because malicious actors know they are the easiest to get as they themselves wrongfully assume that they are not a worthy target.

The most common WordPress security issues that arrive on my desk are the results of code injections. In fact, a large part of my income for a while was because of a remote code execution vulnerability in a single WordPress plugin that a local agency had kept as a part of their “standard package” of 20 or so relatively unnecessary plugins for their clients.

Negligence can result in real wrecks like this runaway train. (TomMartyn 1974, Wikimedia Commons CC BY-SA)

So what are code injections anyway?

According to the WordPress project’s own security guidelines (namely the article Hardening WordPress), WordPress needs write access to itself in order to run automatic updates which is an unfortunate side effect of traditional PHP web hosting, dating back to the 90’s.

For clarification, this is not the fault of web hosting providers, but the outdated policies of the WordPress project leadership itself, emphasising portability and beginner friendliness above professional best practices and a default configuration and system requirements that really need reconsidering.

The need for such lax file permissions in WordPress means that if your website has a remote code execution vulnerability in one of its themes or plugins, it is likely that anyone on the internet who has the intent to do so can expand that security hole and get in.

This ranges from malicious actors placing their own ads on your site (or replacing your whole site with ads and scams), collecting login credentials or sending themselves a copy of the database, including all the information in there.

Secondly, WordPress allows its administrative users to open up and use the Plugin Editor to modify files within the WordPress stack by default, which is a horrible idea from the security perspective, so a single case of a leaked password can let an outsider inject code into your site. (Hint: This can be disabled by setting the constant DISALLOW_FILE_EDIT to true in wp-config.php).

Less common ways of things going south are access credentials to your hosting provider going astray and security vulnerabilities spreading between sites owned by the same account on the same web server, which can have the same results.

Some considerations

One thing to keep in mind here is that code injections and other security breaches usually do not start with your website going down or starting to glitch randomly all of a sudden. That is simply the end stage and things may have been brewing for a long time.

When it comes to most WordPress sites getting breached, there are usually some automated processes involved in addition to malicious code being injected in seemingly random locations. Those doing the work here are usually foot soldiers with little training or knowledge, not hoodie-wearing twentysomethings.

Those in charge of the breach want to keep your site running while they conduct their own business of stealing information that can then be used for scamming your customers, breaking into their accounts elsewhere etc.

In the case of well known and heavily exploited vulnerabilities letting outsiders in, you may even have more than one malicious party wreaking havoc on your site.

If your website is vulnerable, then it is guaranteed to be breached — and it doesn’t matter if you’re a government agency, a San Francisco tech company with thousands of employees, a bike shop in Berlin or a roller derby team from Reykjavik.

There are even search engines out there specialised in exposing vulnerabilities of websites and other online services ranging from security cameras to websites running on systems like WordPress and one of them could be yours.

If a WordPress site suddenly starts working again, it hasn’t been “unhacked”

Needless to say, breached WordPress sites can come back online as suddenly as they went offline after an automated update or after someone deleted a line of code they found in one of the core files in the WordPress installation.

You may think this is a good thing, but any malicious actor wants to keep your site alive as they know they can continue to wreak havoc, steal personally identifiable information and destabilise your business.

At least have your site examined by a specialist, even if this happens, especially if you are running an online store front or have a contact form installed.

The tools at hand

WP-CLI

This is the most important one. WP-CLI is a powerful set of command lines tools that expose and extend WordPress’ core functionality such as updates, plugin management, user management and even the editorial process itself.

In our case, we can manipulate the WordPress installation without loading the themes and plugins by using the --skip-plugins and --skip-themes flags, making it more likely for things to work.

WordPress plugins can even hook into WP-CLI to offer their own command line interfaces.

The part I use the most for security audits is integrity checking for the WordPress Core and plugins from the wordpress.org repository. This compares file signatures and sees if there are any missing or stray files dotted around your WordPress installation.

Furthermore, make sure not to run WP-CLI as an administrative user (root) when recovering breached sites.

Standard Unix/Linux tools

Obviously, being able to look at and change files from the command line is useful for this sort of work.

Using standard tools like cat, less and grep and an editor like vim or nano to glimpse at files in the terminal is how I do things, but if you have a file manager included in your hosting package, then you can use it too for the same purpose.

PHP Malware Scanner

Regardless of being convoluted, this is a really important tool to use. You can install PHP Malware Scanner with PHP Composer or simply clone the code repository from Github and run it like that.

PHP Malware Scanner covers the ground that WP-CLI can’t; such as themes, plugins from outside wordpress.org and the uploads directory, where malicious code can be kept and run from elsewhere. (even if you can’t run a .php file using a web request to your site at /wp-content/uploads/, other less suspicious looking files can still run them internally).

The downside here is that a lot of the malware patterns that PHP Malware Scanner looks for are standard practice in WordPress development (such as encoding icons with PHP’s base64_encode function, which is also used for obscuring malicious code) and simply put, it can spit out loads of false alarms, resulting in manual checking, which only gets more efficient with practice.

WordPress specific vulnerability databases

  • Patchstack: This is my preferred tool, as it goes as aggressive as ethically possible on zero-day attack vectors that have not been announced publicly but may be in active use. The web interface is great, even for non-registered users.
  • WPScan/wpvulndb: One of Automattic’s acquisitions, this is the most commonly used WordPress specific vulnerability database out there. They offer a simple web interface and a paid subscription for their JSON API, but as with Patchstack, we will stick with using the web interface.

The methodology

This describes the method that I generally use for sites that are still salvageable. This can also be done with an older backup of the WordPress installation, but I prefer to start with the site in an as-is state and go from there.

Close the shop

Check with your hosting provider if it possible to display an “under maintenance” page instead of the WordPress site while you work on it. Sometimes, it is sufficient to direct request for the site to a different web root on the same server, with a single index.html file in there.

This makes sure that nobody else accesses the site while it is being recovered. You also don’t want to play whack-a-mole with the malicious actors that took your site down while you’re trying to fix it.

Once you’re done, you can reopen the site.

Backups

You may be able to ask your hosting provider to restore a backup of your WordPress installation from before your site broke down. Sometimes this needs to be requested via email, while larger hosting providers offer self-service tools for that.

Don’t use it to overwrite your current WordPress installation right now, but having it within reach can be useful as a last resort and do keep it in mind that this older version of the site still has the vulnerability that let the malicious actor in to begin with.

Also, do make sure to download a copy of your site in its current state as well as the database. You may be able to use your hosting provider’s web interface for this, but the best tools are something like an SFTP or Git.

Preparation

After manually backing up your site, start by observing the root directory of your WordPress installation. This is where your index.php and wp-config.php files are located.

Are there any files in there that do not belong there or have weird or nonsensical names?

Is there a subdirectory (hidden or visible) that does not belong there either, that may also have strange files in it, even if they don’t have a name that ends with .php?

Now check if wp-config.php and wp-settings.php have any suspicious code in them. Anything unusual in those files may prevent tools like WP-CLI from working properly as they depend on those files and wp-config.php is expected to be edited, so it is generally not checked by automated tools even if it can contain code that should not be there.

You may use your hosting provider’s file manager feature for this, but I prefer to use the command line interface with commands like ls -al, cat and less making things quick and easy.

If you have any doubts about which files and directories should be in there or what their contents should be, check the original WordPress source code and do a quick comparison if you need to.

A good rule of thumb here is that if anything looks like scrambled text, it shouldn’t be there.

Scanning and repairing the WordPress core and plugins with WP-CLI

This is where we start scanning your WordPress core and its integrity, and you need command line access (sometimes called Shell, Terminal or SSH) in order to do that.

This command checks all the files that came with the WordPress core and sees if there are any stray files that should not be there:

$ wp core verify-checksums --skip-plugins --skip-themes

In case of you getting a list of files that are labelled as not verified against a checksum, it means that they have potentially been injected with malicious code.

In that case, your output may look like this:

Warning: File doesn't verify against checksum: wp-includes/version.php
Warning: File should not exist: wp-includes/images/totally-legit-file.ico
Error: WordPress installation doesn't verify against checksums.

Here we see that wp-includes/version.php has been modified, potentially with malicious code. There is also a file here in the wp-includes/images/ directory that looks very suspicious. In fact, a lot of the malicous code thatt ends

In that case, or if the above command fails, you may want to re-download WordPress, you can run the following command:

$ wp core download --force --skip-plugins --skip-themes

Run the core verify-checksums command from above again and simply delete the files it does not recognise. This should leave the WordPress core itself in a usable state, but we are only getting started as the plugins are next.

Scanning and repairing plugins from WordPress.org

$ wp plugin verify-checksums --all --strict --skip-plugins --skip-themes

This works in a similar way as the wp core commands from above. It iterates through the plugins installed on your site and checks the integrity of its files. The --strict option enables checks for minor changes as well, including new files and such.

The results may look like this:

+---------------------+-------------------------+-------------------------+
| plugin_name         | file                    | message                 |
+---------------------+-------------------------+-------------------------+
| plausible-analytics | plausible-analytics.php | Checksum does not match |
| plausible-analytics | ev0lh4x.php             | File was added          |
+---------------------+-------------------------+-------------------------+
Error: No plugins verified (1 failed).

Now, here we have a plugin that fails its test because plausible-analytics.php has been modified and the file ev0lh4x.php has been added.

Do note that WP-CLI will not be happy about plugins that did not come from the wordpress.org plugins repository but I will discuss them in the next section, scanning and repairing anything else, but please make sure to note them down.

For the next step, have a look at the following:

$ INSTALLED_PLUGINS=$(wp plugin list --field=name --skip-plugins --skip-themes)
$ wp plugin install $INSTALLED_PLUGINS --force --skip-plugins --skip-themes

Those two scary looking commands list the currently installed plugins and then reinstall the plugins from that list. As with the integrity check, it does not reinstall plugins that did not come from wordpress.org.

After reinstalling the plugins, run the plugin verify-checksums command again to see if the free plugins have any added or modified files this time – and as with the WordPress core, make sure to delete the stray files that shouldn’t be there.

Deleting unused plugins and themes

A lot of breaches happen due to plugins and themes that have been disabled but not uninstalled completely. List out the currently installed plugins and themes and evaluate what should and should not be there:

$ wp plugin list --skip-plugins --skip-themes
$ wp theme list --skip-plugins --skip-themes

It is up to you to remove the unused plugins and themes from the filesystem or if you want to use WP-CLI for it.

Scanning and repairing anything else

Depending on the amount of paid plugins and themes, or ones that simply did not originate from wordpress.org, you are going to need to decide which ones to get rid of and which ones to salvage.

Themes can also not be checked for integrity in the same way as plugins, so it is best to get rid of the ones you are not currently using.

There are also directories in your WordPress installation outside the core, plugins and themes that need to be checked and deleted. Malicious files are very very often included from your uploads directory even if they can’t be run from there, often disguised as image files.

Start by installing PHP Malware Scanner, which you should be able to do as long as you have shell access.

You can for example download the codebase from Github and run it from your home directory, if you can’t do that, you can install it to your local machine and run it from here instead after downloading your WordPress installation via SFTP or your hosting provider’s file manager.

In case the path to your WordPress installation is ~/public_html/, you can scan your themes like this:

$ ~/php-malware-scanner/scan -L -p -k -x -b -E -d ~/public_html/wp-content/themes

This command looks for suspicious patterns in the themes that you still have as a pat of your WordPress installation. Observe each file listed with a text editor to ensure that noting malicious is in there.

This is where you need to judge if the file itself should or shouldn’t be there at all or if some of the fractions are injected code, if at all.

The more often you end up doing this you start knowing how to spot the malicious code patterns that are usually wrapped in base64 encoded text or use other form of obfuscation, that look nothing like WordPress or anything related to it.

I also recommend doing this scan anywhere else you can think of within the site, including the wp-content/uploads directory to make sure that nothing is hidden in there.

Checking and renewing licences for paid plugins and themes

Here is the boring part: Are your paid themes and plugins being updated and are you able to re-install them?

In many of the cases that end up on my desk, the web design and development agencies that worked on the sites only paid for a one-year licence for the theme and paid plug-ins that they installed. By that time, those licences have expired.

This means that you will have to find where it was from, pay for it again and re-install it if you want them to stay secure and freshly updated.

Audit users

An important step is to make sure that there are no malicious or unnecessary users with administative or editorial access to the site.

The following WP-CLI command list all users on the site:

$ wp user list

If you get a lot of users that are subscribers or WooCommerce customers, you can use the --role=administrator parameter to filter users who have admin rights for instance.

Are there any users that have admin privileges but don’t need them? You can either delete them using them $ wp user delete $user command or demote them to a subscriber using $ wp user set-role $user subscriber.

Reset all passwords and sessions

This destroys every user session, effectively logging everyone out.

$ wp user session destroy --all --skip-plugins --skip-themes

Then, you can reset everyone’s password. If email facilities are configured for the site, the users will get an email about the reset with a link for them to take care of it themselves.

$ WP_USERS=$(wp user list --field=id --skip-plugins --skip-themes)
$ wp user reset-password $WP_USERS

Closing the file and looking up possible causes

You probably want to write a report about the breach and what may have possibly caused it. You can look the originally installed version of the WordPress Core in addition to the plugins and themes that were there in the beginning in one of the vulnerability databases.

Remember that in some cases, the plugin or theme in question does not need to be active in order for a vulnerability to take its toll.

The aftermath

Getting breached is humiliating in more ways than one and the effects of that can range from loss of customer trust or a drop in SEO to or your site being listed as malicious by search engines and browser vendors.

And that is awful, especially for those who’s main source of income is their web presence.

Legal obligations

In case your site was breached using code injection or stolen login credentials or any other way; even if the website itself kept running, you may have certain legal obligations towards your users and customers.

If you have been collecting personally identifiable information in any way; including registered users, contact forms and WooCommerce orders, you will need to contact your data protection authority (i.e. the relevant government authorities) or a lawyer including the next steps in order to assess the situation and for advice on the next steps.

Some of the things you may need to do is to announce the breach to those individuals who’s information may have been accessed by a 3rd party.

Learning from it all

After recovery (or during the intensive care phase), there are usually questions that can be asked and best practices that can be implemented, including the following:

  • Do I need to have all those plugins installed?
  • Does every user on the site need administrative powers?
  • Were there registered users that should not be there?
  • Are my own login credentials safe?
  • Should I implement and mandate multi-factor authentication for logging in to my site?
  • Is the site in its current form causing me liability that I could be without?
  • Was our choice of hosting provider a negative or positive factor?
  • Is WordPress right for me and my business?
  • Did we choose a competent agency to work on the site?
  • Did we as a business conduct sufficient due diligence when having the site developed?
  • Did the agency that worked on the site support us or accept responsibility?
  • Did the agency have any form of responsibility here at all?
  • Should we have had a support contract for the site from the web development agency?

None of those questions are wrong and for anyone conducting the recovery effort, those questions should be the basis of a final report. Security necessitates turning as many stones as possible and admittance and acceptance of facts are key to that.

Keep it in mind that the work may not even be over and that one needs to keep an eye on things for at least a week or two to make sure that things don’t happen again. Examine the site logs and use some form of application monitoring if possible.

There are no magic solutions or silver bullets here. The methods I describe here do not apply everywhere and in all cases but this should at least demonstrate what goes into attempting to recover a WordPress site after it has been breached and injected with malicious code.