Here you
are, but
should you
have come?

Clean Up a Hacked, Compromised WordPress Site

by Wayne Thursby

It happens to the best of us. In spite of our efforts to keep our WordPress instances up-to-date, a plugin or theme falls behind and some miscreant uses that to ruin things.

At that point, it's tempting to just remove the bad stuff and move along. However even if a thorough job is done, and WordPress core matches checksums, it's entirely possible for the attacker to waltz right back in unless the WordPress security keys in wp-config.php are updated.

The home for this information is truly on the WP OrbitalStrike GitHub Page but I'm reproducing it here for convenience.

Since every WordPress installation is different, there's no way to completely automate the process of cleaning up after a compromise, but this doesn't mean we can't sharpen our tools for the manual work.

Keep the good stuff

It should go without saying that a backup should be made before doing any of this. While part of the process is to try and ensure everything is backed up, that shouldn't be good enough; make another backup.

Note that all of these scripts assume you're running them from the document root of the WordPress installation.
wp-backup.sh

#!/bin/bash

tmp_dir=`mktemp -d`
echo Work Directory: $tmp_dir

files_to_keep=("wp-config.php" "wp-content/uploads")

for i in ${files_to_keep[@]}; do
  echo "Copying ${i}"
  cp -R ${i} $tmp_dir  # Replace with mv for speed once it's working right
done

echo "Backing up WordPress Database and saving data"
wp db export $tmp_dir/database.sql
wp core version > $tmp_dir/wp_version.txt
wp plugin list --format=csv > $tmp_dir/wp_plugins.txt
wp theme list --format=csv > $tmp_dir/wp_themes.txt

do_not_compress=".tiff:.gif:.snd:.png:.jpg:.jpeg:.mp3:.tif"

zip --suffixes $do_not_compress -1 -r $tmp_dir/backup.zip -x@"exclude.txt" .

echo "Finished backing up to: $tmp_dir"

Get rid of the hacked, compromised stuff

Now the next step is to delete the WordPress installation. It's at this point you'd need to make sure to keep any extra directories like a downloads folder, or any subdirectories that might contain other web applications. This part is up to you, but should be totally safe because you created that backup previously, right?

Reinstall From the WordPress Repository

Here comes the cool part. We'll need to read the info in the files we created earlier, and use WP-CLI to reinstall WordPress, themes, and plugins. Care is taken to record and reinstall the correct version. As long as the plugin is published correctly in the WordPress repository, this will result in a nicely working installation.

Of course if you're using plugins or themes that aren't in the repository, you'll need to manually download and reinstall those.

Prior to running this script, there should already be a wp-config.php in the directory, and it should be referring to a MySQL user and database that have been created.

wp-restore.sh

#!/bin/bash

source_dir=$1

wp_core_version=`cat "$source_dir/wp_version.txt"`

# Download and extract the WordPress core
wp core download --version=$wp_core_version

# Read the list of themes from the file we created earlier
INPUT=$source_dir/wp_themes.txt
[ ! -f $INPUT ] && { echo "$INPUT file not found"; exit 99; }
while read -r line
do
	theme=`echo $line | awk -F"," '{ print $1 }'`
	isactive=`echo $line | awk -F"," '{ print $2 }'`
	version_number=`echo $line | awk -F"," '{ print $4 }'`
	
	if [ "$isactive" = "active" ]
	then
		isactive="--activate"
	else
		isactive=""
	fi
	if [ "$theme" != "name" ]
	then
		wp theme install $theme --version=$version_number $isactive
	fi
done < $INPUT

# Read the plugins from the file we created earlier
INPUT=$source_dir/wp_plugins.txt
[ ! -f $INPUT ] && { echo "$INPUT file not found"; exit 99; }
while read -r line
do
	plugin=`echo $line | awk -F"," '{ print $1 }'`
	isactive=`echo $line | awk -F"," '{ print $2 }'`
	version_number=`echo $line | awk -F"," '{ print $4 }'`
	
	if [ "$isactive" = "active" ]
	then
		isactive="--activate"
	else
		isactive=""
	fi
	if [ "$plugin" != "name" ]
	then
		wp plugin install $plugin --version=$version_number $isactive
	fi
done < $INPUT

# Copy the wp-config and uploads directories back.
cp $source_dir/wp-config.php .
mkdir wp-content
cp -R $source_dir/uploads wp-content/uploads

# Reset WordPress security keys
# Found on StackOverflow http://stackoverflow.com/a/16389269/6060612
# Not even marked as the correct answer, but this does the trick even with existing keys

find . -name wp-config.php -print | while read line
do
    curl http://api.wordpress.org/secret-key/1.1/salt/ > wp_keys.txt
    sed -i.bak -e '/put your unique phrase here/d' -e \\
    '/AUTH_KEY/d' -e '/SECURE_AUTH_KEY/d' -e '/LOGGED_IN_KEY/d' -e '/NONCE_KEY/d' -e \\
    '/AUTH_SALT/d' -e '/SECURE_AUTH_SALT/d' -e '/LOGGED_IN_SALT/d' -e '/NONCE_SALT/d' $line
    cat wp_keys.txt >> $line
    rm wp_keys.txt
done

And there you have it! This should be a working WordPress installation. Note that we dumped the database but didn't do anything with it. The same database is present. This means that for the small percentage of compromises that persist in the database, more work is necessary to get things back into shape.

This project is maintained by Wayne Thursby | Check out the Github repo | Design by: unaghii.