Sysadmin: How to cleanup compromised Wordpress site


Last week I had really hard time dealing with the server that was hit by multiple Wordpress vulnerabilities in a row. Not to mention some outdated versions and cpanel account password leakage because of the server owner. During the long hours I’ve devised more or less effective procedure for cleaningup compromised Wordpress sites that’s proven relatively effective, given the circumstances. The precondition is that that I don’t have any access to the site backup, and didn’t have much time to examine particular theme or plugin (oh there were few custom built and long discontinued plugins that I had to preserve). So, to the business at hand.

Prerequisites

You should have SSH access to the server and above average Linux CLI proficiency. You will be using wp-cli tool to process compromised Wordpress sites (wpc in further text), if you don’t have it on the server already, upload it and make executable. Posession of the site backup is desirable but not strictly necessary. If you have it all, lets begin.

Site cleanup procedure

  1. First and foremost – delete all unknown files in site root directory. Restore main .htaccess file to the default Wordpress state, disable all plugins, that require additions to .htaccess. Unless of course you have off-site backup, then you should restore from backup. At that point would be wise to check that files permissions and owhership allow you to delete and overwrite files and nothing is set to read-only. Normal files should have permission 644 (-rwxr–r–) and directories 755 (drwxr-xr-x), anything more is insecure (there are some special cases though, depending on hosting provider setup, I am talking about most basic situation)
  2. Second – examine configuration file wp-config.php visually for suspicious modification – usually it’s the beginning of the file or the end.
  3. Now to wpc. First step is to find out what version of Wordpress we are dealing with wpc core version At the moment of writing, current Wordpress version is 6.3.2, all previous version are insecure. You can try to update Wordpress core files to latest version with wpc core update command. WP-CLI will try to download lastest available Wordpress version (of the same language you have installed) and try to update existing installation effectively overwriting current Wordpress core files. Depending on your server configuration this step might fail and you will need to update manually (more on that some other time). Lets assume this step finished successfully.
  4. Lets verify Wordpress core files integrity (in case you already have latest available version installed) – wpc core verify-checksums. The output of this step should be checked carefully – Red color of the result line doesn’t necessary mean that Wordpress core compromised – missing readme.html or wp-config-sample.php trigger an error but not a sign of security breach. Sometimes there are lot of error_log files inside wp-includes or wp-admin directories – that usually happen on cpanel servers and not a reason for concern. Any PHP or other file added should be deleted, files with mismatched checksum should be replaced. Quick way to do so would be utilizing the same wpc ( wpc core download --skip-content --force will get you latest WordPress core files without overwriting your content and plugins)
  5. Next step – check for added admin users that should not be there wpc user list --role=administrator. Say we have confirmed proper admin user with ID 2 and added rogue user with ID 5, to get rid or added user execute wpc delete 5 --reassign=2, where reassign means user ID who will get all content from deleted user.
  6. Now to the tricky part – wp-content directory is not checked for integrity except plugins, registered and present at wordpress.org/plugins site. Delete all unfamiliar .php files under wp-content directory first. If some file name looks legitimate check inside – you will be able to spot malicious file. Just don’t be lazy and scroll the file all the way down – sometimes it could be couple of lines at the end that make the file into backdoor.
  7. Lets check what we have installed with wpc plugin list. Delete all plugins with obscure names, without version conformation or ones that you don’t remember installing. Either with removing wp-content/plugins/ directory or using wp-cli with wpc plugin delete <plugin-name> command. You can also get all plugins updated to the latest version while you are at it- wpc plugin --all update
  8. Now lets try to verify known plugins files integrity – wpc plugin --all verify-checksums. If you see some plugins with a lot of files added or or modified it would be wise to note the name and just delete this plugin to reinstall it later. If you are notified about plugin not being found on official site that could mean that this commercial plugin provided separately or custom written specifically for your site, in both cases better to remove this plugin and reinstall from trusted source.
  9. Now, other directories under the wp-content is very tricky – your mileage may vary. As the rule of thumb there should not be any PHP files under uploads or upgrade or upgrade-temp-backup as well as gallery and ngg – all these PHP files should be treated as malicious
  10. Themes – not to be trusted at all. Ideally wiped and reinstalled from trusted source. If this is impossible, then all files should be examined for typical backdoors keywords (eval,error_reporting,@include, base64_decode and such) which could be any – meaning sometimes backdoor files could be named as .css or .png.
  11. Database should be examined for malicious insertions – these will be redirects and unauthorized links as well as fake pages, posts and sitemaps
  12. Last but not least – you should change SQL user password (in case it’s the same as FTP user password the latter should be changed too) as well as WP administrator users passwords

If you feel overwhelmed by such instruction you can always contact me and we can get something arranged.

Leave a Comment

NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">