
How-to: Removing malware and backdoors from WordPress
On this page
This article was published on February 20, 2019 on damianstrobel.de - as part of consolidations, articles that have some relevance to IT security are also hosted on the DSecured blog.
If a WordPress installation is compromised after a successful attack, there are two options - either you delete everything and start from scratch, later import the database and your files and (hopefully) end up with a clean installation. The other option is to work on the active installation, searching for backdoors and shells. In my view both methods are equally valid and quickly lead to the same dilemma - more on that later.
Introduction & Reasons for Successful Attacks
WordPress has an astonishingly large share of the web. Around 25% of websites on the internet are based on WordPress - so if someone finds a vulnerability in WordPress or a well-known plugin, they can gain access to a large number of sites and from there infect further sites, distribute malware to visitors, store warez, perform blackhat SEO through poor linkbuilding, or simply steal data from large websites. Ultimately, enrichment and money are the reasons for many attacks on the web.
If an attack is successful, you suddenly have various problems - someone has access to your server, your data has been stolen, malware spreads, or the server is used as a spam relay. Acting quickly can save you a lot of trouble.
Below you will find a guide on how to efficiently clean such sites so you can continue working on them. I recommend reading the article to the end - often simply replacing infected files with the original files is not enough - at that point you need to know what you are doing or hire a professional!
Block the Site from the Outside
As a first step you should isolate the infected site - or the server - from the outside. It may also be that your hoster already does this automatically for security reasons. Then backdoors cannot be actively used and while you clean the site/server nothing new can be added. Beginners often make the mistake of leaving sites online, start removing shells and backdoors, and notice they removed 10 of them while 2–3 new ones appeared in parallel. Malware in the form of backdoors tends to replicate.
The easiest way to isolate a site from the outside is to add a simple “deny from all” to the .htaccess. Or just put a simple htaccess login in front of it that only you know. Depending on the server/hosting provider there are of course other options - just ask!
Your site should now be reachable ONLY by you - as I wrote above I assume that your hoster allows SSH access to your webspace. That simplifies your life enormously. You can work directly on the server, so you don't have to download the site. The alternative is of course to download the site and install it locally in a virtual environment (never on your own PC!). For this I use, for example, virtualbox.org.
Change Important Credentials (SSH, FTP, Hosting)
Your site is now unreachable to others. Now change all important credentials - that means: FTP credentials, SSH credentials, database credentials and finally the credentials to your hosting provider. Most of this will probably not be necessary, because we assume that only your site was hacked and not you or your hoster, so the attacker usually only obtained the database credentials. Those who stored FTP credentials in wp-config.php - which is not recommended - should of course also change those credentials.
To continue working you should at least leave the new database credentials in wp-config.php via FTP or SSH. Since the site is not reachable from the outside, you don't need to worry that someone can read those credentials.
Do not visit the site itself. That means you work either within the SSH console or via FTP. If you enter your website, for example because it is protected by an htaccess login, you may trigger a backdoor yourself! Be careful!
Make Backups of Files and Database
Before you change anything on the site or “clean” it, it's recommended to make a complete backup of your site. Since I assume you have SSH access, you can do this in two commands. Log in and change to the directory where the installation resides:
Create a database backup:
mysqldump -h localhost -u USERNAME -p DATABASE_NAME > sql-dump-ds.SOMERANDOMNAME.sql
Enter the database password, and a database backup will be created. Of course you must adapt the parameters.
Archive all files (including the DB backup) into a zip:
zip -r backupn0w.zip . --exclude *.zip --exclude *.tmp --exclude *.bck --exclude *.tar --exclude */cache/*
This command compresses all files in the current folder - excluding certain files. You can then either move the backupn0w.zip internally or download it via sftp/ftp. That gives you a complete backup of your site. Simple and efficient.
Delete Irrelevant Files and Folders
Before you get to the actual cleaning, it's worth deleting old files you don't need - things like cache folders, maybe private folders and files - basically everything that doesn't belong to WordPress. The idea is to minimize the number of files and folders to analyze and at the same time remove potential hiding places for backdoors and shells. Malware is quite good at hiding! Keep that in mind.
This step can of course be skipped if you believe there is nothing to remove or it's unnecessary.
Update Core, Theme and Plugins via SSH
WordPress essentially consists of core files, themes and plugins. Shells usually hide somewhere in the folders within your installation. To avoid searching every theme and file by hand - using wp-cli is recommended. With wp-cli you can perform various actions on your installation via the console (SSH). That ranges from installing plugins or themes to replacing strings in the database. You can install an entire WP with desired plugins, content and theme in seconds with wp-cli. wp-cli can do even more. I’ll present some important commands and show how to use them:
Inspect WP Core for changes
php wp-cli.phar core verify-checksums
This command allows you to calculate the checksums of every WordPress core file. This quickly shows if the WP core has been modified by a third party. That's often the case with malware.

Here it's obvious that the first file is not part of WordPress and does not belong there. Inspecting such a file reveals whether it's a shell or not. The same applies to wp-config.bak.php - it could be a shell - or just a backup of wp-config.php. I usually skip this step - I'm not interested in what is in the core. I delete the core files directly and replace them with a fresh installation. That leads us to the next command:
There is an analogous command for plugins:
php wp-cli.phar plugin verify-checksums
Replace core files with one command
php wp-cli.phar core update --version=5.0.3 --force
The command is simple - the current core is deleted and replaced by the WordPress 5.0.3 core. IF malware was there, it's now gone.
We do the same now for plugins and the theme:
Replace Plugins & Theme with wp-cli
php wp-cli.phar plugin install $(php wp-cli.phar plugin list --field=name) --force
This command deletes every plugin and installs it again. Again - if a shell or backdoor was hidden in any folder, it's now gone. This updates directly to the current version of the plugin. At the end there is a short report that you MUST look at CAREFULLY! The result looks roughly like this:

What does this tell us? Of 17 plugins, 15 could be successfully removed and replaced with the original version. We can assume those plugins are now clean. Nothing should be hiding there anymore!
15 out of 17, however, means there are 2 plugins that could not be replaced. That can have several reasons. They might be custom developments or plugins that were removed from the official repo for security reasons. Or they are plugins you obtain from third parties (envato.com, …) - and there you might have forgotten to enter the tokens that would allow updates. In the case of external plugin vendors you can either add the tokens and often update via wp-cli, or remove the plugin, download it from the original source and upload it again.
Here we come to the first dilemma. These are plugins or folders where malware can hide. These must be inspected by hand. The topic “inspecting by hand” is complex, requires PHP knowledge and especially IT security knowledge. I know few people who would really recognize a vulnerability. The simple solution is to just delete such a plugin, but that is not always possible. So: be careful!
The command for themes is analogous:
php wp-cli.phar theme install $(php wp-cli.phar theme list --field=name) --force
Again - if you use custom-developed themes, you will have to review them manually! I also recommend that plugins and themes not actively used should not only be deactivated but really deleted from the server. That reduces attack vectors and the number of hiding places.
Some hosting providers already have WP-CLI integrated. For others you have to download it first. Also check which php binary can run php-cli. On my systems it's “php”. In cases like All Inkl it might be something like: php7.0.2-cli. If you don't know exactly, ask the provider and explain that you intend to use WP-CLI.
Current Status
We have now replaced a large part of the site with clean versions of core, themes and plugins. However, the WordPress system also has folders that you ALWAYS have to check manually. A short and certainly incomplete list follows:
- plugin-specific folders inside wp-content (cache/, ewww/, nfwlogs/, pomo-editor/, …)
- uploads folder: wp-content/uploads
- multi-site plugins: wp-content/mu-plugins
- temporary upgrade folder: wp-content/upgrades
- language files: wp-content/languages
- main folders for plugins and themes
- child themes
In addition there is the database, which we haven't looked at yet.
Manual Analysis: wp-content folder, languages folder, custom folders, …
Backdoors can be located in any of these folders. In the case of wp-content/languages it does not even have to be a PHP file. You can hide backdoors in language files that you won't find at a glance. This is a classic beginner mistake by people trying to make money in the “WordPress hacked” space. I already discussed this in my article “ Does WordFence protect my WordPress site as well as everyone claims?”. There you will find, for example, a .mo file that you can upload and include as a language file. Known pseudo-protection plugins like WordFence will not detect this backdoor - and most service providers won't either.
In the case of multi-site plugins, the command mentioned above to replace all plugins also affects mu-plugins - a valid plugin there would also be replaced and possibly only the old malware-ridden leftovers remain. So you need to check that.
In the uploads folder generally no PHP files belong. Unfortunately some plugins have started placing PHP files there. So you must be careful when deleting things there.
In general shells and backdoors can be found in such folders fairly easily; for example, for the uploads folder:
find wp-content/uploads/ -name "*.ph*" -print
Note that besides normal php files there are executable files with extensions like php7 or pht or phtml. In general shells do not necessarily have to hide in these files - PHP code can also be hidden, for example, in image files. So scanning is worthwhile - this is not trivial because often the attacker has a way to include the image file via PHP and thus execute the code - ultimately there is some “Local File Inclusion” in the system.
Still - at this point you usually have a bunch of plugins and files that a layperson will find hard to evaluate. This is the moment where you simply delete the suspect file - better safe than sorry. An alternative is roughly described here: Finding backdoors and vulnerabilities with grep. But that's just an intro with the 10 most important commands - reality is much more complex. You need to know how attacks work, how vulnerabilities appear, what they look like and how to find and close them. At this point a layperson and sometimes even programmers will struggle.
Search the Database, Change Admin Passwords
Once the file level is clean, it's worth looking into the database. You should at least have a basic handle on MySQL and know how WordPress's database structure works. A lot of nasty stuff can hide in the database. I won't go into the special and very rare cases of serialized backdoors here. More common is that hidden administrators were created via a shell. In phpMyAdmin (or the DB tool of your choice) this often looks like when viewing the wp_users table:

The suspicious registration date “0000-00-00 00:00:00” is a reliable indicator that something is wrong. It's not 100% certain though. Admin accounts created via XSS look normal. The simplest rule here is: delete all accounts that you don't recognize - via wp_usermeta you can find out which rights each user ID has. Watch out for those with “user level” 10 - that's an administrator. This is not a sufficient criterion - even more precise is the prefix_capabilities value:

What also frequently occurs: in wp_posts - the table that contains content - additional entries are left that usually serve blackhat SEO. You can simply delete these. You must, however, go through them manually. A good tool is of course MySQL and the LIKE %%-query to narrow the results.
Also search for “<script” and suspicious filthy terms - in the post_content column you often see directly that someone left junk. This can be removed either manually or automated. The corresponding MySQL command for this would be REPLACE.
Change admin passwords - the quick method
Don't forget to change the passwords of all valid administrator accounts. I don't bother much and randomly alter the user_pass column inside wp_users and force the users to reset their passwords themselves later - of course with the note not to reuse the old password. In the worst case the attacker has that password and will return soon!
Basically we're done, …
Essentially that's it. The chosen installation is now probably clean. Probably… the subject is complex and if you do something wrong or don't have the experience to really find backdoors, especially when you have many custom files and custom developments, you might miss something. Then the work was in vain. So always consider whether you really want and can do this yourself. The actual “cleaning” is a very fast process if SSH is available.
The real time sinks in these processes
If no SSH is available you will have to use FTP. Even if the server allows many parallel connections, it takes time to download a site, clean it and upload it back. If you can't wait, you can zip all files via PHP or hosting, download the zip, work locally, and then re-upload the cleaned instance in a zip. You can then unpack it via PHP. Libraries like unzipper are useful for this. But don't forget to delete everything on the server first and only then unpack. That's why I'm generally a fan of hosts that offer SSH. It simplifies everything enormously.
Another problem is updates. The reason for successful attacks is usually unmaintained installations that have vulnerabilities. Those must be closed, so you perform all updates - whether the client wants it or not. Most of the time everything goes well, the site becomes faster and more secure. But there are cases where that is not the case. Plugins may no longer be compatible or maintained and then cause problems you have to deal with.
Accordingly it's hard to estimate how long the “cleaning” really takes. In the best case you're done in 30 minutes, in the worst case it takes hours or days. The latter especially for large sites with a lot of custom code.
Topic: Special plugins that allow embedding PHP, JS or CSS
Especially the great multipurpose themes from Envato allow embedding CSS and JS code. Attackers love to use these functions to simply inject their code into the frontend, which then executes for the user. The nice thing about this method is that you don't see it directly because you first suspect some backdoors, shells or malware plugins. The same applies to plugins - there are many that offer such functions. A quick look at the relevant fields in the backend or, better, via MySQL is always worthwhile.
You'll usually find code there that displays fake ads to visitors or redirects them to gambling sites abroad.
Topic: Vulnerabilities
For me, besides the semi-automated cleaning of a WordPress instance, searching for vulnerabilities that still exist despite updated plugins is also part of the work - for example because plugins are used that haven't been maintained for a long time but the client doesn't want to give them up. Cleaning alone does you absolutely no good if there is still a vulnerability somewhere in the supposedly clean installation. Finding and discovering vulnerabilities is an art and generally time-consuming. I'm active on, among others, hackerone.com - I actively look for vulnerabilities there and receive so-called bug bounties. For example, anyone who manages to execute code on twitter.com via hackerone may get around 20,000 USD.
Many providers skip this step. I don't think that's good - in the end the client wants a clean and secure WordPress instance. If that's not the case it only leads to client frustration.
Nobody expects you to sit 10 hours in front of 15 WP plugins. But it's worth taking 1 hour to look more closely at the source code of the smaller installed possibly unmaintained plugins and the theme. This is by no means comparable to a complete source code audit or pentest, but it allows you to quickly tell the client whether a plugin is well or poorly developed. Stupid and simple vulnerabilities are often found incidentally if you have an eye for it.
Topic: Prevention
Instance clean and all vulnerabilities closed? There's still more you can do. Depending on the case and what's possible on the chosen server, it's worth implementing file and user permissions on the server. Especially at agencies with reseller servers I repeatedly see that all customers run under a single user. If one site is hacked, all others are necessarily penetrated and effectively hacked. This can cost thousands of euros to clean up.
File and user permissions
Accordingly: implement user and file permissions. Ernesto Ruge has a nice article about this. Another advantage of proper file permissions is that an attacker, even if successful, often cannot write to a large part of the folders and files. The malware cannot spread. That's extremely valuable - especially considering that most malware is distributed by completely automated and stupid scripts.
Disable PHP in certain places
WordPress naturally has writable folders (e.g. uploads) - a simple trick is to disable PHP execution exactly in those folders. Create a .htaccess in the respective folder. The .htaccess could look like this:
RemoveHandler .php .php2 .php3 .php4 .php5 .php6 .php7 .pht .phtml RemoveType .php .php2 .php3 .php4 .php5 .php6 .php7 .pht .phtml php_flag engine off
Even if someone uploads a backdoor there, it will not be executable. That gives you time to react.
Keep the system up to date
Also: apply updates at all costs! The faster, the better!
Protect important directories
Furthermore - especially if you are the only admin: add an htaccess login/password protection to the wp-admin folder. This is a simple and effective protection against unauthorized access! Such an htaccess can look like this:
AuthType Basic AuthName "My Protected Area" AuthUserFile /var/............/httpdocs/.htpasswd Require valid-user <Files admin-ajax.php> Order allow,deny Allow from all Satisfy any </Files> <Files admin-post.php> Order allow,deny Allow from all Satisfy any </Files> <Files "\.(css|gif|png|js)$"> Order allow,deny Allow from all Satisfy any </Files>
Topic: Monitoring
If you already have SSH access it's worth monitoring file changes regularly. I recommend this command:
find $1 -type f -print0 | xargs -0 stat --format '%Y :%y %n' | sort -nr | cut -d: -f2- | head
As soon as a shell is written it will appear at the top. Furthermore, there are programs for those who run their own servers that automate this task. I also recommend the PHP firewall “NinjaFirewall (WP Edition)”. As a concrete protection this WordPress plugin is quite useful. It protects you extremely well against a variety of attacks - including in the backend. That is not the case, for example, with WordFence.
Topic: Restoring an Old Backup
If you are sure you have a backup that is truly clean, you can of course shorten the entire process and restore a backup, update and hope everything goes well. The reality is that successful attacks often only become noticeable after months. That means older backups are often already infected. Often you go in circles here and it's rarely a viable option. Moreover it often means data loss.
Conclusion
Cleaning a previously hacked WordPress instance is - if the server and the available options are adequate - with the help of special tools like wp-cli, child’s play and quick to do. But as the devil is in the details - you can't automate and rush everything. If you want to be absolutely sure, you must thoroughly check which plugins are used and what the development status of those plugins is. Prevention and subsequent monitoring are also important topics where many service providers often fail.