How to restore a WordPress site after hacks or exploits in 10 easy steps


wordpress-hackedA lot of people use WordPress, and seemingly a lot forget to click the upgrade button regularly enough and find they are exploited.  In an ideal world, you would click that button whenever you see it needs updates, and have copious amounts of good backups. However, sometimes these things do not happen, and you need to bring the site back from being exploited, and you need it done quickly.

This is a how to, which will explain the quickest and best method for upgrading those sites and getting them back online.

UPDATE UPDATE UPDATE
I have now written a script to automate this.

wget http://blog.rimuhosting.com/files/restorewordpress.sh
chmod +x restorewordpress.sh
./restorewordpress.sh /full/path/to/documentroot 

Note: This does not do custom themes or plugins (only ones from wordpress.com), and its a good idea to double check the wp-config.php is clean, either before, or immediately after doing this.

Manual Instructions Below

Step 1: Disable the website, either in the apache config, or point it to another DocumentRoot with a ‘be back soon’ page somewhere.

Step 2: Move the entire DocumentRoot somewhere else. Potentially all the files in it are contaminated, and usually are.

mv public_html public_html-bak

Step 3: Download a clean version of WordPress, unzip and move it to your DocumentRoot

wget http://wordpress.org/latest.zip
unzip latest.zip
mv wordpress public_html

Step 4: copy the wp-config.php from the old one, once copied, open it in an editor and check for any code that should not be there (eval, exec, or other php code – that file strictly has variable setting only)

cp public_html-bak/wp-config.php public_html/
nano public_html-bak/wp-config.php

Step 5: Check what plugins were installed on the old WordPress, find them on http://www.wordpress.org/plugins/ and download the zip files. Unzip these into wp-content/plugins/

ls public_html-bak/wp-content/plugins/
cd public_html/wp-content/plugins/
wget http://downloads.wordpress.org/plugin/wp-super-cache.1.3.2.zip
unzip wp-super-cache.1.3.2.zip
rm wp-super-cache.1.3.2.zip

Step 6: Do the same for the themes http://wordpress.org/themes/ . If you have a custom theme, get a developer or somebody to check for added code (php, javascript, other files)

ls public_html-bak/wp-content/themes/
cd public_html/wp-content/themes/
wget http://wordpress.org/themes/download/twentytwelve.1.2.zip
unzip twentytwelve.1.2.zip
rm twentytwelve.1.2.zip

You can script this to automate updating to the latest themes/plugins using the API by using the following script. Put this into a fix.sh ; bash fix.sh

#!/bin/bash
# Edit this first line
wp_root=/var/www/some/path/to/wp-root
new_wp_root=/var/www/some/path/to/new_wp-root

echo Upgrading plugins
        rm -rf /tmp/plugupdate.txt
        pluglist=$(find $wp_root/wp-content/plugins/ -maxdepth 1 -type d | sed s@$wp_root/wp-content/plugins/@@)
        for plugname in $pluglist ; do curl -k -L -s http://api.wordpress.org/plugins/info/1.0/$plugname.xml |grep download_link | cut -c40- | sed s/\].*// >>/tmp/plugupdate.txt ; done
        for file in $(cat /tmp/plugupdate.txt) ; do curl -k -L -s -o /tmp/tmp.zip $file ;unzip -qq -o /tmp/tmp.zip -d $new_wp_root/wp-content/plugins/ ; rm /tmp/tmp.zip ; done

        echo Upgrading themes
        rm -rf /tmp/themeupdate.txt
        themelist=$(find $wp_root/wp-content/themes/ -maxdepth 1 -type d | sed s@$wp_root/wp-content/themes/@@)
        for themename in $themelist ; do numchars=$(echo $themename | wc -c) ;numchars=$(($numchars-1)); curl -k -L -s -d 'action=theme_information&request=O:8:"stdClass":1:{s:4:"slug";s:'$numchars':"'$themename'";}'  http://api.wordpress.org/themes/info/1.0/ |sed -n 's|.*http\(.*\)zip.*|http\1zip\n|p' >>/tmp/themeupdate.txt ; done
        for file in $(cat /tmp/themeupdate.txt) ; do curl -k -L -s -o /tmp/tmp.zip $file ;unzip -qq -o /tmp/tmp.zip -d $new_wp_root/wp-content/themes/ ; rm /tmp/tmp.zip ; done
~                          

Step 7: Check file ownership on all the files in plugins and themes dir, make sure they match the ones in the old exploited WordPress.

[root@server wp-content]# ls -l themes/
total 20
rwxr-xr-x 2 root root 4096 Oct 24 2012 hum
-rw-r--r-- 1 youruser youruser 30 Apr 15 2009 index.php
drwxr-xr-x 7 root root 4096 Apr 19 11:34 montezuma
drwxr-xr-x 8 youruser youruser 4096 Aug 1 16:49 twentythirteen
drwxr-xr-x 7 youruser youruser 4096 Aug 1 16:49 twentytwelve
[root@server wp-content]# chown -R youruser.youruser themes/
[root@server wp-content]# chown -R youruser.youruser plugins/

Step 8: Copy the uploads directory over from the old site, and any other directories you may have added that you need.

cp -a public_html-bak/wp-content/uploads public_html/wp-content/

Step 9: Check all files are clean of exploits or not PHP files of any kind. Look for any cgi scripts or hidden directories. Upload dir rarely if ever contains php or cgi (usually just images/media)

find public_html/wp-content/uploads -type f -iname "*php"
# to check image files are what they say they are you can do this
find public_html/wp-content/uploads -type f | while read file ; do file "$file"; done
./public_html/wp-content/uploads/2012/10/Disney-703x288.jpg: JPEG image data, JFIF standard 1.01, comment: "CREATOR: gd-jpeg v1.0 (using IJ"
./public_html/wp-content/uploads/2012/10/institute_imagen2-150x150.jpg: JPEG image data, JFIF standard 1.01, comment: "CREATOR: gd-jpeg v1.0 (using IJ"
./public_html/wp-content/uploads/2012/10/Group_Disney-150x150.jpg: JPEG image data, JFIF standard 1.01, comment: "CREATOR: gd-jpeg v1.0 (using IJ"

Step 10: Re-enable the website in apache or change the DocumentRoot back. Then browse to http://yoursite.com/wp-admin/ – this will run any database upgrades if they are needed.

This should get things fixed and going, and usually easily done in under 15 mins if its has no custom themes or mass amounts of plugins etc. Now you just need to just test and debug anything else that is not working, occasionally some things break, but not often.

One of the first things you should do once logged in is check the users, and make sure there are no Admin users, and that all Admin users have their passwords changed.

Here is an example of an ‘extra’ admin hackers added

mysql> select ID,user_login,user_status,user_pass from wp_users;
+----+------------+-------------+------------------------------------+
| ID | user_login | user_status | user_pass                          |
+----+------------+-------------+------------------------------------+
|  1 | admin123   |           0 | 4297f44b13955235245b2497399d7a93   |
|  2 | iamgay     |           0 | $P$BhP0q4Dz3eKhbbJArDZSFkilj157zj. |
+----+------------+-------------+------------------------------------+
2 rows in set (0.00 sec)

Change the passwords like this (Change the ID for the one you want)

mysql> UPDATE `wp_users` SET `user_pass`= MD5('newpassword') WHERE ID='1';
Query OK, 2 rows affected (0.31 sec)
Rows matched: 2  Changed: 2  Warnings: 0

From here you can edit pages or  to remove any added exploit code that was inserted into the databases. If you have a LOT of code in a database and want to remove it all at once, you can use mysql via command line or phpmyadmin, and the replace command like this

UPDATE wp_posts SET POST_CONTENT = replace(POST_CONTENT, 'exploitcode', 'somethingelse');

If you get stuck and need a hand with any of this, by all means pop in a support ticket and we can take care of that for you.