Recovering from a Hacked WordPress Site

(…another in a series of posts mostly for myself – so I’ll have a place to remember things….but maybe the random visitor will find something useful here.)

One of the sites I visit is a Q&A forum type place about WordPress development. And there is always a question about ‘how do I fix a hack of my WordPress site?’.

If you ask the googles, you’ll find lots of answers; some are actually good answers. Some aren’t. So I thought I’d put down what I would do if I was in that situation. (I’ve had to do this before, for other sites and clients.)

Step 1: Secure Access

If your WP site gets hacked, someone got into the site somehow. Doesn’t really matter how they did. So my first step is to secure access to the site.

This means that I will first change the password on my hosting place. Strong passwords, of course, and one that I have never used before (or will use elsewhere).

Next, I look at the external access to my files. This means looking at all FTP user accounts, and changing the ones that I use to develop or access the site. (Or in the case of client sites, their access.). And looking for user accounts that I don’t recognize. Those will get immediately deleted. Then I will create a brand-new FTP user account, again with a strong password. All other FTP accounts will get deleted.

Then I look at the credentials for the WP database. I’ll change the password for that database. If the site is well-visited, I might create a new database user, assign it to that database. The access rights for that database will be limited; nobody gets all of the possible database rights.

Since I have changed (or added) the database credentials, I’ll go into the WP config file where those credentials are stored and change it to the new account. A quick check then to ensure that the site still works. If it is OK, I’ll delete the old database user account.

Just for grins, I’ll also change the passwords for any email accounts on that site. That is probably not needed, but couldn’t hurt. I’ll just have to remember to notify those email accounts of the change. Most of my sites are single-user (me), so that’s usually not a big deal

Step 2: Update everything

Next, I’ll log into the admin area of the site. I’ll create a new admin-level user, give it a strong password (of course), and then log out and log in with that new user account. Once logged in, I’ll delete the old admin account.

While in the user accounts area, I’ll create a user called ‘admin’. Strong password, but that account gets the lowest privileges. (Some hack attempts like to try to use the user called ‘admin’. So, I let them try.)

Next, I’ll look at any other user accounts. If there are several or more, I’ll screenshot the list, and then reduce their access levels to the lowest privileges. I’ll fix that access later when things are cleaned up.

Now, into the Admin, Update screen and reinstall the latest version of WP.

The next step is a bit more time-consuming – deleting and reinstalling all themes/plugins. Not just installing, delete everything first, then reinstall as needed. Hopefully, I’ve remembered to keep the number of active (and inactive) themes and plugins to the minimum. (Usually a good idea not to have plugins that aren’t active. Just delete them.)

To reinstall all plugins, I have to delete them first. Many may have some complex settings screens, so screenshots of those are done (and printouts if needed). Once I’ve documented all plugin settings, I’ll deactivate and then delete them all – using the option (if presented) to delete the files associated with the plugins.

This may break the site, of course, or at least not make it work like it normally does. So I may do this during low-activity times. (Some of my sites are always low-activity, so I just ‘do it’.)

After all plugins are fully removed, I start reinstalling the ones that are needed, using my reference screenshots/printouts to re-set the options for each as needed. I only use plugins from the WP repository, even the ones that I write. That way, I make sure that I get the most current version of the plugins – even though I check and update plugins (and themes) daily.

Once I have finished with all of the plugins, I do the same for themes. Screenshot the settings, delete the themes (files included), then reinstall from the WP Theme Depository. Put back all the settings (from my screenshots) as needed.

In both cases, I may use my FTP client to look at the theme and plugins folders to make sure that they have actually been deleted. Then I’ll reinstall.

A check of the site to make sure it works OK completes this step.

Step 3 – Investigate

Now I have (with any luck) a clean WP install, with current (and clean) themes and plugins.  I need to make sure that anything left over is not going to cause a problem.

First, I check the htaccess file. I know what that file should look like (look at the WP Codex for help), so I make sure that the file is how it is supposed to be.

Next, I use my favorite FTP program (WinSCP) to log into the site – after changing the user/pass to the new one. I then poke around all of the site folders for files that I don’t recognize. I usually sort by date (oldest first), and look for files that are earlier than today. Remember that I reinstalled everything, so everything that is valid should have today’s date stamp. I don’t rely on that date sort, though, I look at all of the files; the date sort is just the first pass through all the files.

And I look at all site folders, not just the root folder. Rouge files will jump out because of their name or datestamp. I sometimes move those rouge files into a non-site folder in case I want to look at them later. As I move files, I make sure the site is till working properly.

Step 4 – Product (ecommerce) and other files

I take a look at all of the media files on the site, on the off-chance that one of them is compromised. But usually those aren’t a problem.

What might be a problem is ecommerce/product files. It may be that a product file is the compromised access point. I’ll look at every single product, every single field for that product, to make sure that all is well. That can be a bit time-consuming, but so can a re-infection if a product is the infection point.

Step 5 – Posts and Pages and Comments

It may be that a post or page has some rouge code in it. Now, by default, WP will not execute code inside a post/page. But, on the off-chance it does, I want to ensure that all posts/pages are OK. Depending on the site, I may use a SQL command or two to look for indicates of code inside a page/post. If it is a smaller site, I’ll just look at all posts/pages. I may sort by date, if I am aware of the approximate date that the hack got into my site.

It’s not likely that a Comment will be the infection point, since I do have some protection against spammers – via my own plugins  (FormSpammerTrap for Comments) and Akismet. But a quick look at Comments is a good idea. I set up the anti-spam features to immediately discard all potential spam, so usually don’t have a problem with that.

Step 6 – Final Cleanup

By this point, the site should be back to normal. I’ve been checking the site as I go through each step, looking for indications of continued infection.

If I had to reduce privileges on user accounts, I put those back to normal.  And I make sure that backups are in place and working. Not just the database, but all files (WP, themes, and plugins).

Winding Up

The above steps are a really good start at cleaning up a site. Some would argue that a ‘Nuke from Orbit’ – starting from scratch by deleting everything, but that may not be an option for most sites. My procedure takes a bit of time (many hours – more than 8, usually), so it’s not easy. (And, if I am doing it for a client, it’s not inexpensive.)

But, I’ve been successful in site cleanups before with this process.

What do you think? Add your ‘thinky bits’ in the comments.

Changing Contact Form 7 with the wpcf7_before_send_mail Hook

Some notes about using (and perhaps abusing) the Contact Form 7 process in a WordPress site. (Contact Form 7 is a popular plugin for WordPress sites that lets you easily create a Contact Us type form. We use it here, although we’ve also tweaked what it does with our FormSpammerTrap for Contact Form 7 plugin that effectively reduces Contact Form spam. You can check out that plugin – and our other FormSpammerTrap spam-bot blocking techniques at our site.)

We were inspired by an article we found via the googles while searching for information on CF7 hooks, because we wanted to do things to the CF7 form content after it was submitted, but before the form was emailed. The article is here. The code is theirs, but we wanted to explain it in more detail (mostly for our own purposes, as we wanted to use a similar process for our own purposes).

Let’s take a look at what we can do after the Contact form is submitted, and before CF7 emails the message. Our intent is to change the Subject line of the form to something other than what the visitor entered. You could easily use this code for your own purposes.

First, lets ‘hook’ into the wpcf7_before_send_mail process. This is done with the standard WP add_action function. (Remember that you can ‘hook’ into any WP function that has a ‘hook’. Ask the googles how the add_action thing works.) Here is the code that you would place in your Child Theme’s function.php file (or perhaps in a plugin):

The add_action code
add_action( 'wpcf7_before_send_mail', 'my_change_subject_mail' );

This will wait for the hook to be called, and run the my_change_subject_mail() function.

Now we need to create the my_change_subject_mail function. I’m going to show and explain each line separately. We’ll put the entire function at the end.

This defines the function:

Defining the function
function my_change_subject_mail($WPCF7_ContactForm)

This will get the current WPCF7_ContactForm object. We need this object to change the subject value therein.

Get the current WPCF7_ContactForm object
$wpcf7 = WPCF7_ContactForm :: get_current() ;

Here we get the Submission object, which is generated when the user hits the ‘send’ submit button.

Cet the WPCF7 Submission Object Instance
$submission = WPCF7_Submission :: get_instance() ;

Next, let’s ensure that the contact form has been submitted.

Check if there is a submission of the contact form
if ($submission)

If the form is empty, exit the function.

Exit the function if it the contact form is empty
if ( empty ($posted_data))
return ;

This is how we read a value of the form. The basic CF7 Contact Form has variables, this next line references the variable that was defined as ‘your-message’. If you want to work with another field on the form, use that field name as the parameter of the $posted_data array.


Read the your-subject field contents into the $subject variable
$subject = $posted_data['your-message'];

At this point, you could do a search/replace on the subject, or anything else. For instance, maybe you’d like to store the message fields in a database. You would set various variables – like we did with the $subject variable – to values from the $posted_data array. Then you could store those variables into a table in your database.

We’re not going to do anything with the $subject variable, we just wanted to show you that it could be done.

Our intent is to change the ‘subject’ of the email to something else. First, we have to read the property of the $mail object. This is the object that CF7 uses to create the email.

Get the contact form's values into the $mail array
// do some replacements in the cf7 email body
$mail = $WPCF7_ContactForm->prop('mail') ;

Since the subject line in our CF7 form is called ‘subject’, here is how we change the contents of that field:


Change the Subject field's value
$mail['subject'] = "this is an alternate subject" ;

Now that we have changed a value in the $mail array, we need to set that value in the WPCF7_ContactForm object:

Store our new subject back into the CF7 ContactForm object
// Save the email body
$WPCF7_ContactForm->set_properties( array("mail" => $mail)) ;

If we are curious about all of the contents of the WPCF7_ContactForm object, we could quickly write it to the error.log file. This is OK for our purposes, as we are on a development site, and the error.log is a quick way to look at something. Note that you can’t ‘echo’ or ‘print’ anything to the screen in this process; it won’t be shown.

Look at all the contents of the object
// error_log( print_r( $WPCF7_ContactForm, 1 ) );

All done. We’ll return the object just in case it is needed.

Exit out of the function
return $WPCF7_ContactForm ;

By looking at the contents of the object, you might find other things that you might like to change. The $mail part of the object is stuff that will be emailed.

The result of all of this is that we have changed the subject line of the emailed message with out little function. We could add additional parts (maybe some extra data like the IP address of the sender, or whatever) to the $mail array -maybe the message content –  and that would also be sent.

All done!  Any questions, use the comments. We’ll try our best to muddle through an answer to your question.

Here’s the entire function:

The Entire Code
add_action( 'wpcf7_before_send_mail', 'my_change_subject_mail' );
function my_change_subject_mail($WPCF7_ContactForm)
$wpcf7 = WPCF7_ContactForm :: get_current() ;
$submission = WPCF7_Submission :: get_instance() ;
if ($submission)
$posted_data = $submission->get_posted_data() ;
// nothing's here... do nothing...
if ( empty ($posted_data))
return ;
$subject = $posted_data['your-message'];
//$subject = substr($this->replace_tags( $template['subject'] ), 0, 50);
// do some replacements in the cf7 email body
$mail = $WPCF7_ContactForm->prop('mail') ;
$mail['subject'] = "this is an alternate subject" ;
// Save the email body
$WPCF7_ContactForm->set_properties( array("mail" => $mail)) ;
// error_log( print_r( $WPCF7_ContactForm, 1 ) );
// return current cf7 instance
return $WPCF7_ContactForm ;

URL Smashing

We run a few WordPress sites – some for us, and some for others. One of the things that we noticed on a couple of sites was that long URLs were visually irritating, at least to us. They seem to get in the way of the content, and sometimes ‘bleed over’ the content areas.

So we went looking for a solution – a plugin. And all the ones we found were not simple enough. They required you to enter special codes, or other irritating things. We wanted one that worked automatically. And we couldn’t find one that we liked.

The result – our second WordPress plugin – “URL Smasher”. It uses the shortening service, so requires a Google API account, but those are free and easy to get. Once you set it up by adding your Google API key, and checking two boxes, any content that is saved – posts, pages, or comments – that have URLs get them automatically shortened.

It works quite well. Like the URL for the previous post. I entered the actual URL as text: ,  and as a link. Each is automagically shortened when I save or publish the post.

You’ll find the plugin here at (also shortened).

We are quite impressed with ourselves.

FormSpammerTrap for Comments WordPress Plugin

My new WordPress plugin to block form spammers/bots is now publicly visible at . It blocks comment spam from ‘bots’ with a simple technique. It doesn’t have captchas, hidden fields, silly questions, or other things that don’t work. It just looks for ‘human activity’ on the comment form, and if a ‘bot’ tries to submit a comment, they immediately get sent to my FormSpammerTrap web site.

It uses the same techniques that I use for comment forms (more info about that here and here and here ), but now it is a WordPress plugin, so it is quite easy to install and configure. And it is quite effective…I’ve never gotten any ‘bot’ spam on any site that I have installed it.

The whole plugin coding process was interesting, and a good learning process. I’ve already got some enhancements in mind for newer versions. But I was quite proud of myself for getting this one to work…and that it is now available among the millions of other WordPress plugins.

Two New Web Sites

I’ve finished two new web sites, and continue with updates on a few more. I am also working on a WordPress plugin to prevent spam-bots from abusing comment forms. That one is a bit more tricky, but useful knowledge.

The two new sites are WordPress-based.

John D Brown Author Site : this is the author’s official site. I found his site when I read his book “Bad Penny”. It is a thriller, with a “Jack Reacher” type character. I enjoyed it, and went to his web site to see if there were other books in a similar vein. While on his web site, I emailed him to suggest a few design changes for his site. And ended up doing a customized rewrite of his site into a ‘responsive’ site that looks good on any device – laptop, phone, desktop, etc. Along the way I increased my WordPress customization expertise, creating changes in a child theme plus adding additional customized functionality. He and I were pleased with the results. And it let him concentrate on writing the next book with the same character as “Bad Penny”.

The Hot Box Grills site is an e-commerce site that sells a nice tailgate/picnic portable grill. Well made and sturdy, and works quite well as a portable grill, according to his satisfied customers. His previous e-commerce site wasn’t working well, and was hard to manage. The new place has a responsive theme, and I am working on his SEO stuff. If you are looking for a great portable BBQ grill for picnics, camping, or sporting events, check it out.

Ultimate Form Spammer Blocking

A while back, I wrote about a technique to block form spammers. I have implemented it on several sites that I have built. In some cases, the site originally didn’t have any protection against form spammers, or used easily-bypassed techniques like hidden fields, silly questions, changing contact page names, or even captchas. Even with those techniques, form spam still arrived.

In most cases, the volume wasn’t enough for me to worry about. Sometimes, a form spammer would find one of my site forms, and start spamming it daily. At that point (when it started getting irritating), I would implement my ‘ultimate form spammer blocking’t technique.  And the form spam immediately stopped; the form spammer never was seen again on that site.

So I have put together a package of files that you can use in your site forms to get rid of your form spam. It’s all open source, and quite clever (he said with some modesty). With just a few modifications of your form, and adding one or two files to your system, you can get rid of your form spammer problem.

The package of files is written in PHP, and requires that your visitor has JavaScript running (not Java). But once you set things up, you will not be bothered by form spam again. Your form spammers will get redirected to my Form Spammer Trap site instead of sending your form spam.

The package includes support for WordPress sites, with the use of a template that you specify for your contact page. No special add-ins needed, although you may need to do some minor CSS formatting changes. Full instructions on how to implement in your site, whether PHP-based or a WordPress site, are included in the package.

So, how do you get it? Well, you use our contact form. Just fill in the form with your name, email address, and comment text of “I want your form spammer blocking package” (along with any other comments), and I’ll get the zip file out to you. Don’t use the comment form on this post, unless you really want to expose your email address.

Our Contact form uses our Ultimate Form Spammer Blocking technique. If you want to see what will happen to a form spammer, just click on the “Submit” button on the contact page (don’t click anywhere else).  We use the WordPress template version of the package, so you can see how it looks with our site theme.

And it is all free, although donations are accepted. If you don’t think you can install it yourself, contact me and we’ll arrange for some help at a nominal fee. Or if you don’t want to use our package, the technique is shown on our previous post about preventing form spam, so you can roll it yourself.

But our ‘Ultimate Form Spammer Blocking’ package will get rid of any form spam on your web site. It really works!

(Added: If you want more details, go to my Form Spammer Trap web site that uses the technique. That site is where form spammers will end up.)

A WordPress Attack

It appears there is an attack against WordPress installations that is placing a phony ‘500’ error page on the site that allows additional commands to be executed. I don’t have all the details yet, but one report indicates that there is a brute-force password guessing attack against the ‘admin’ user of a WordPress site.

The ‘admin’ user is created by default on a WordPress installation; that user has full privileges to the WordPress installation. If the owner has chosen a weak password, or ohe that is easily guessed, then the attacker would get full admin privileges to the WordPress site, including the administrative area.
WordPress login process allows for brute force attacks; an unsuccessful login will just let you try again. There might be some delays if you try brute-force logins, but it is possible to keep on trying a WP login.
The attack will put a phony ‘500.php’ file in your site root (and perhaps other places). So a search for those files might be prudent. Delete any that contain unfamiliar code.
Initially, it looks like many sites that have been successfully attacked are also not current in their WordPress version level. So, prevention would indicate these steps:
1) Create a new ‘admin-level’ user with a strong non-dictionary type password.
2) Log in as that user to ensure that all is OK
3) When logged in as the new admin-level user, demote the user ‘admin’ to the lowest level. Leave the user there just to irritate the hacker.
4) Ensure that your hosting account, and any FTP accounts, have strong passwords. Strongly consider changing FTP passwords.
5) Don’t use an FTP client that stores passwords in plain text. (WinFTP does this.). I recommend WinSCP (open source, free) which encrypts FTP credentials.
6) Ensure your WordPress installation is current. Update all themes and plugins on a regular basis.
7) Check for any rogue user accounts
And the usual precautions on your home computer: Windows updates, Application updates (Secunia Personal Software Inspector is recommended), uninstall Java (if it is not needed; Javascript is OK), don’t clck or open unfamiliar attachments, etc.
As a further protection, consider a program that monitors files for unauthorized changes. I found a concept for a program that stores file names and checksums in a database, then compares those checksums the next time you run the program. Any new or changed filenames are emailed. I am doing some final testing, but it appears to work well.
Be careful out there!