After making an entry to my WordPress blog, I noticed that my RSS subscription service did not list the new entry for several days. When I examined the server files, I found out that my blog had been hacked. The wp-blog-header.php had been replaced, and the hacked header file was designed to redirect referrals from search engines to other web sites. Various WordPress sources recommended updating to the latest version of the software, but some people reported being hacked even after updating. The new WordPress code and the anti-spam plugins like Akismet and image captcha were not preventing this problem.
Analyzing the WordPress code, it seemed that the hacking was made possible through buffer overflows because the code did not validate the comment data before using it. Even the "comment blacklist" specified through the WordPress administration panel was invoked by options-discussion.php only after a potentially harmful comment had been processed by the compact() subroutine.
I modified wp-comments-post.php to analyze the raw comment data and go to an error page when anything was invalid or unacceptable. My error page has some counters that let me monitor hacking attempts. Below is a listing of wp-comments-post.php with an example of the code that I inserted. The code restricts the length of the comments, and checks the comment contents, author names, author IP, and author domain. Since I implemented this code, I have monitored several hacking attempts that have been thwarted. You can customize your own blog code using these basic ideas. The comment validation can be extended further by performing a basic linguistic analysis to verify that the average word length of the comments and the ratios of function words like prepositions, articles, and conjuctions are within acceptable ranges for the language of the blog. In English, for example, the 10 most frequent words (the, be, of, and, a, in, he, to have, it) account for about 25% of the text.
Modifications to wp-comments-post.php
<?php
require( dirname(__FILE__) . '/wp-config.php' );
nocache_headers();
$comment_post_ID = (int) $_POST['comment_post_ID'];
$status = $wpdb->get_row("SELECT post_status, comment_status FROM $wpdb->posts WHERE ID = '$comment_post_ID'");
if ( empty($status->comment_status) ) {
do_action('comment_id_not_found', $comment_post_ID);
exit;
} elseif ( 'closed' == $status->comment_status ) {
do_action('comment_closed', $comment_post_ID);
die( __('Sorry, comments are closed for this item.') );
} elseif ( 'draft' == $status->post_status ) {
do_action('comment_on_draft', $comment_post_ID);
exit;
}
$comment_author = trim($_POST['author']);
$comment_author_email = trim($_POST['email']);
$comment_author_url = trim($_POST['url']);
$comment_content = trim($_POST['comment']);
// If the user is logged in
get_currentuserinfo();
if ( $user_ID ) :
$comment_author = $wpdb->escape($user_identity);
$comment_author_email = $wpdb->escape($user_email);
$comment_author_url = $wpdb->escape($user_url);
else :
if ( get_option('comment_registration') )
die( __('Sorry, you must be logged in to post a comment.') );
endif;
$comment_type = '';
if ( get_settings('require_name_email') && !$user_ID ) {
if ( 6 > strlen($comment_author_email) || '' == $comment_author )
die( __('Error: please fill the required fields (name, email).') );
elseif ( !is_email($comment_author_email))
die( __('Error: please enter a valid email address.') );
}
if ( '' == $comment_content )
die( __('Error: please type a comment.') );
// **************************************************
// **** BEGIN custom check to prevent hacking *****
// **************************************************
if ( strlen($comment_content) > 2000 ) { // Reject very long comments
wp_redirect('http://www.website.com/errorpage.html',301); die();
}
if ( strlen($comment_content) < 20 ) { // Reject very short comments
wp_redirect('http://www.website.com/errorpage.html',301); die();
}
if ( stristr($comment_content, "transsexual") ) { // Reject comments with specific words
wp_redirect('http://www.website.com/errorpage.html',301); die();
}
if ( stristr($comment_author, "transsexual") ) { // Reject comments from specific authors
wp_redirect('http://www.website.com/errorpage.html',301); die();
}
$posty_ip = $_SERVER['REMOTE_ADDR']; // Get IP address
if ( $posty_ip == "78.46.198.162" ) { // Reject comments from specific IP addresses
wp_redirect('http://www.website.com/errorpage.html',301); die();
}
$comment = get_comment($comment_id);
// Reject comments from authors with specific IP addresses
if ( stristr($comment->comment_author_IP, "78.46.88.142") ) {
wp_redirect('http://www.website.com/errorpage.html',301); die();
}
$comment_author_domain = gethostbyaddr($comment->comment_author_IP);
// Reject comments from specific domains
if ( stristr($comment_author_domain, "your-server.de") ) {
wp_redirect('http://www.website.com/errorpage.html',301); die();
}
// **************************************************
// **** END custom check to prevent hacking *****
// **************************************************
$commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'user_ID');
$comment_id = wp_new_comment( $commentdata );
if ( !$user_ID ) :
$comment = get_comment($comment_id);
setcookie('comment_author_' . COOKIEHASH, $comment->comment_author, time() + 30000000, COOKIEPATH, COOKIE_DOMAIN);
setcookie('comment_author_email_' . COOKIEHASH, $comment->comment_author_email, time() + 30000000, COOKIEPATH, COOKIE_DOMAIN);
setcookie('comment_author_url_' . COOKIEHASH, clean_url($comment->comment_author_url), time() + 30000000, COOKIEPATH, COOKIE_DOMAIN);
endif;
$location = ( empty( $_POST['redirect_to'] ) ) ? get_permalink( $comment_post_ID ) : $_POST['redirect_to'];
wp_redirect( $location );
?>