Advertisement
Plugins

Building a WordPress Security Plugin: The Basics

by

WordPress is open source, which means that everyone, including hackers with a malicious intent, can scour the source code looking for holes in its security. In this set of tutorials, we will be going through the process of creating a WordPress plugin to detect and fix any security flaws that may lie in your WordPress installation.


Step 1 Setup & Metadata

Go ahead and make a new folder in your plugins directory called safe, then create a file called safe.php inside it. The first few lines of code for a plugin are just telling WordPress that it is a plugin, what it's called and other information of that nature. This section is similar to the metadata of a theme or template.

/*
Plugin Name: Safe
Plugin URI: http://wp.tutsplus.com

Description: The easiest, most effective way to secure your WordPress site from attackers.
Author: Fouad Matin
Version: 1.0
Author URI: http://wp.tutsplus.com/author/fouad
*/

Update: As per Christopher's comment below, there is no longer any need to define WP_CONTENT_URL, WP_CONTENT_DIR, WP_PLUGIN_URL, and WP_PLUGIN_DIR as WordPress defines them for us in the file includes/default-constants.php.


Step 2 Action & Filter Systems

Next we have to put in place some action hooks, which are functions triggered after a certain event happens, such as the admin_menu function being called. Actions are typically used for modifying database data, email messages, and modifying what is displayed on the screen. Filters, on the other hand, are hooks that are launched prior to rendering text or sending data. You can also remove actions if you want, such as the wp_generator function.

remove_action('wp_head', 'wp_generator');

Step 3 Define the Actual Functions

First, before adding any of our text into the plugin, we are going to essentially embed a stylesheet using the wp_enqueue_style function. This function is just a safe way to add CSS files to a WordPress generated page. For more information on enqueuing, check out Japh's tutorial on including javascript and css.

add_action('admin_enqueue_scripts','safe_styles');
function safe_styles(){
	wp_enqueue_style('safe_style', plugins_url('/css/safe.css', __FILE__));
}

Now, in order for the user to be able to open the plugin page, we need to add a menu page for our plugin. Also, the security plugin should only be available to administrators (you can change this if you want).

add_action('admin_menu', 'add_menu_bpg');
function add_menu_bpg()
{
	if (!current_user_can('administrator'))
	{
		return false;
	}

	if (function_exists('add_menu_page'))
	{
		add_menu_page('Safe', 'Safe', 'edit_pages', 'safe-admin-page', 'safe_main', WP_PLUGIN_URL.'/safe/img/safe.png');
	}
}

Step 4 Metaboxes

In order to display information on the plugin page, we use metaboxes to make it look consistent with the WordPress admin theme. We are going to want to display the system/server information and also run some basic, quick security checks.

function safe_meta_box()
{
?>
	<div id="safe-basic-checks" class="safe-inside">
		<div class="safe-basic-checks-section"><?php safe_check_version();?></div>

		<div class="safe-basic-checks-section"><?php safe_check_table_prefix();?></div>

		<div class="safe-basic-checks-section"><?php safe_version_removal();?></div>

		<div class="safe-basic-checks-section"><?php safe_errorsoff();?></div>

		<div class="safe-basic-checks-section"><?php safe_wpConfigCheckPermissions('/wp-config.php');?>
	</div>
<?php

Now, let's check if there is an account with the default setting of admin which is the first guess of most hackers and therefore an easy way into your admin panel. Also, I recommend you configure an .htaccess file in your wp-admin directory that blocks all IP addresses other than yours. In our next article, we will set up a way to configure the .htaccess from the plugin page.

	global $wpdb;

	echo '<div class="pass">WP ID META tag removed form WordPress core</div>';

	echo '<div class="safe-basic-checks-section">';

		$name = $wpdb->get_var("SELECT user_login FROM $wpdb->users WHERE user_login='admin'");
		if ($name == "admin")
		{
			echo '<span class="fail">"admin" user exists.</span>';
		}
		else {
			echo '<span class="pass">No user "admin".</span>';
		}

	echo '</div>';

	echo '<div class="safe-basic-checks-section">';
		if (file_exists('.htaccess'))
		{
			echo '<span class="pass">.htaccess file found in wp-admin/</span>';
		}
		else {
			echo '<span class="fail">The file .htaccess does not exist in the wp-admin section.</span>';
		}
	echo '</div>';

?>
	</div>
<?php
}

Let's add another column/box for the right side of the screen, where we can display all of the server's information and settings.

function safe_meta_box2()
{
?>
	<ul id="safe-information-scan-list">
		<?php safe_get_serverinfo(); ?>
	</ul>
<?php
}

Step 5 Main Functions

Just for organizational purposes, create a new directory named inc inside your safe directory, where you will create a file called functions.php to hold our main functions. These functions will be responsible for running the tests, checking file permissions, database settings, etc. It's better just to separate these from the main plugin file so that it's easier to navigate and read.

First, start off by initiating a connection to the database and retrieving the MySQL version and MySQL information array. Also, let's check to see if PHP safe mode is turned on, as it may cause some headaches with other plugins and settings.

function safe_get_serverinfo() {
	global $wpdb;
	$sqlversion = $wpdb->get_var("SELECT VERSION() AS version");
	$mysqlinfo = $wpdb->get_results("SHOW VARIABLES LIKE 'sql_mode'");
	if (is_array($mysqlinfo)) {
		$sql_mode = $mysqlinfo[0]->Value;
	}
	if (empty($sql_mode)) {
		$sql_mode = __('Not set');
	}
	$sm = ini_get('safe_mode');
	if (strcasecmp('On', $sm) == 0) { 
		$safe_mode = __('On'); 
	}
	else { 
		$safe_mode = __('Off'); 
	}

In order to accept incoming pingbacks, allow_url_fopen has to be set to "on" in your php.ini. However, it also makes your code susceptible to code injection, because the file_get_contents() can retrieve data from remote locations such as an FTP or web server.

	if(ini_get('allow_url_fopen')) {
		$allow_url_fopen = __('On');
	}
	else {
		$allow_url_fopen = __('Off');
	}

Memory limitations and execution times will play a major role in the next part of the tutorial for the plugin to suggest possible solutions to security flaws while taking the system's limits into account.

	if(ini_get('upload_max_filesize')) {
		$upload_max = ini_get('upload_max_filesize');
	}
	else {
		$upload_max = __('N/A');
	}
	if(ini_get('post_max_size')) {
		$post_max = ini_get('post_max_size');
	}
	else {
		$post_max = __('N/A');
	}
	if(ini_get('max_execution_time')) {
		$max_execute = ini_get('max_execution_time');
	}
	else {
		$max_execute = __('N/A');
	}
	if(ini_get('memory_limit')) {
		$memory_limit = ini_get('memory_limit');
	}
	else {
		$memory_limit = __('N/A');
	}
	if (function_exists('memory_get_usage')) {
		$memory_usage = round(memory_get_usage() / 1024 / 1024, 2) . __(' MByte');
	}
	else {
		$memory_usage = __('N/A');
	}
	if (is_callable('exif_read_data')) {
		$exif = __('Yes'). " ( V" . substr(phpversion('exif'),0,4) . ")" ;
	}
	else {
		$exif = __('No');
	}
	if (is_callable('iptcparse')) {
		$iptc = __('Yes');
	}
	else {
		$iptc = __('No');
	}
	if (is_callable('xml_parser_create')) {
		$xml = __('Yes');
	}
	else {
		$xml = __('No');
	}

Now, we just spit out all of the data that we just collected about the server.

?>
	<li><?php _e('Operating System'); ?> : <strong><?php echo PHP_OS; ?></strong></li>
	<li><?php _e('Server'); ?> : <strong><?php echo $_SERVER["SERVER_SOFTWARE"]; ?></strong></li>
	<li><?php _e('Memory usage'); ?> : <strong><?php echo $memory_usage; ?></strong></li>
	<li><?php _e('MYSQL Version'); ?> : <strong><?php echo $sqlversion; ?></strong></li>
	<li><?php _e('SQL Mode'); ?> : <strong><?php echo $sql_mode; ?></strong></li>
	<li><?php _e('PHP Version'); ?> : <strong><?php echo PHP_VERSION; ?></strong></li>
	<li><?php _e('PHP Safe Mode'); ?> : <strong><?php echo $safe_mode; ?></strong></li>
	<li><?php _e('PHP Allow URL fopen'); ?> : <strong><?php echo $allow_url_fopen; ?></strong></li>
	<li><?php _e('PHP Memory Limit'); ?> : <strong><?php echo $memory_limit; ?></strong></li>
	<li><?php _e('PHP Max Upload Size'); ?> : <strong><?php echo $upload_max; ?></strong></li>
	<li><?php _e('PHP Max Post Size'); ?> : <strong><?php echo $post_max; ?></strong></li>
	<li><?php _e('PHP Max Script Execute Time'); ?> : <strong><?php echo $max_execute; ?>s</strong></li>
	<li><?php _e('PHP Exif support'); ?> : <strong><?php echo $exif; ?></strong></li>
	<li><?php _e('PHP IPTC support'); ?> : <strong><?php echo $iptc; ?></strong></li>
	<li><?php _e('PHP XML support'); ?> : <strong><?php echo $xml; ?></strong>  </li>
<?php
}

Another major flaw in most generic WordPress installs is the use of the default table prefix: wp_. In our next article, we will set up a way to backup and rename the tables which is where the safe_check_table_prefix and safe_errorsoff functions will come into play.

function safe_check_table_prefix() {
	if($GLOBALS['table_prefix']=='wp_') {
		echo '<span class="fail">Your table prefix should not be <em>wp_</em>.</span><br />';
	}
	else {
		echo '<span class="pass">Your table prefix is not <i>wp_</i>.</span><br />';
	}
}

function safe_errorsoff() {
	echo '<span class="pass">WordPress DB Errors turned off.</span><br />';
}

If you have an older version of WordPress, then first upgrade. Regardless of how good you are of how keeping track of upgrades, there is always the possibility that you might fall behind. Once the security flaws of a previous version are listed in the change log for the new version, your installation's security is now compromised. Most themes do it by default but just to be sure, we removed it earlier using the remove_action filter and now we'll just make sure the end-user knows too.

function safe_version_removal() {
	global $wp_version;
	echo '<span class="pass">Your WordPress version is successfully hidden.</span><br />';
}

function safe_check_version() {
	$c = get_site_transient( 'update_core' );
	if ( is_object($c)) {
		if (empty($c->updates)) {
			echo '<span class="pass">'.__('You have the latest version of Wordpress.').'</span>';
			return;
		}

		if (!empty($c->updates[0])) {
			$c = $c->updates[0];

			if ( !isset($c->response) || 'latest' == $c->response ) {
				echo '<span class="pass">'.__('You have the latest version of Wordpress.').'</span>';
				return;
			}

			if ('upgrade' == $c->response) {
				$lv = $c->current;
				$m = '<span class="fail">'.sprintf('Wordpress <strong>(%s)</strong> is available. You should upgrade to the latest version.', $lv).'</span>';
				echo __($m);
				return;
			}
		}
	}

	echo '<span class="fail">'.__('An error has occurred while trying to retrieve the status of your Wordpress version.').'</span>';
}

With the newer versions of WordPress, the security of the wp-config.php shouldn't be a problem, but it's better to be safe than sorry. If someone with malicious intent were to get their hands on your wp-config.php, they could access your database access credentials, authorization keys and database salts. We'll just check if we can get the file, write to the file or if it exists in that directory at all. If it does, we'll show an urgent notice and, in our next article, a method to fix the issue easily.

function safe_wpConfigCheckPermissions($wpConfigFilePath) {
	if (!is_writable($wpConfigFilePath)) { 
		echo '<span class="pass">'.__('Your wp config file is not a threat.').'</span>';
		return false; 
	}

	if (!function_exists('file') || !function_exists('file_get_contents') || !function_exists('file_put_contents')) {
		echo '<span class="pass">'.__('Your wp config file is not a threat.').'</span>';
		return false;
	}
	else {
		echo '<span class="fail">'.__('Your wp config file can be compromised by hackers, fix the permissions.').'</span>';
		return true;
	}
}

Now, we have to go back to the safe.php file and add our new functions.php, above the action and filter hooks.

require_once(WP_PLUGIN_DIR . "/safe/inc/functions.php");

Step 6 Rendering

Back to the safe.php, the last addition is the safe_main() function which is called when the page is loaded. Here we need to add the metaboxes we defined earlier and add some styling.

function safe_main() {

	add_meta_box("safe_box_1", 'Basic Checks', "safe_meta_box", "box1");
	add_meta_box("safe_box_2", 'System Information', "safe_meta_box2", "box2");

	echo '
		<div class="metabox-holder">
		<div style="float:left; width:48%;" class="inner-sidebar1">';

	do_meta_boxes('box1','advanced','');

	echo '
		</div>
		<div style="float:right;width:48%;" class="inner-sidebar1">';
		do_meta_boxes('box2','advanced','');

	echo '
		</div>

		<div style="clear:both"></div>
		</div>';

}

Lastly, make a CSS file named safe.css in a directory called css under our safe directory. You can style the boxes however you want, or you can just use the following CSS code for the error and success text:

div.pass,span.pass {
	color:#7AB317;
}
div.fail,span.fail {
	color:#ff3333;
}
div.fail,span.fail a{
	color:#ff3333;
}

Conclusion

By now, you should know how to add a menu page for your plugin, apply action and filter hooks, and programatically check for any security flaws in WordPress.

If you have any additional suggestions or questions regarding security plugins, feel free to leave a comment! Also, this plugin is on GitHub, so go ahead, fork the repository and customize it however you want.

Update: this article has been updated as per Christopher's suggestions below.

Related Posts
  • Code
    Tips
    New wp-config Tweaks You Probably Don't Know8 new wp config tweaks you probably didnt know about 400
    The wp-config.php file: One of the most loved WordPress feature for some, one of the worst nightmares for others. There are countless tips and tricks in a plethora of articles, and you can't get enough of them; however, this article is aiming to be different. In this article, we're going to get familiar with eight new wp-config tricks that are less known than functionality such as turning off post revisions, increasing the memory limit, or other similar features.Read More…
  • Code
    Theme Development
    Custom Controls in the Theme CustomizerTheme customizer custom control 400
    In the last article, we explored the advanced controls available in the Theme Customizer, and how to implement them. We’re going to look at how to create our own custom control, allowing you to choose which Category of Posts are displayed on the home page. To get started, download version 0.6.0 of our Theme Customizer Example.Read More…
  • Code
    Plugins
    Using HighCharts in WP-AdminHighcharts 400
    Charts are a great way to present data. They make data more digestible by making it visually appealing. In WordPress, there is no built-in method for getting posts and pages data in a graphical form. Although, there are certain plugins available which integrate Google Analytics with WordPress, but they are overkill if you want to get only a portion of that data. Also, nothing should keep you from learning new techniques and to dive straight into the subject is the best way to learn.Read More…
  • Code
    Theme Development
    How to Integrate Bootstrap Navbar Into WordPress ThemeBootstrapd 400
    Have you ever wanted to speed-up the process of theme development? I assume the answer is "yes" and you already know about Bootstrap and use it in mock-ups for development. This raises the question: "How can you integrate Bootstrap components into a WordPress theme?"Read More…
  • Code
    Creative Coding
    Customizing the WordPress Admin - The DashboardCustomize wordpress admin rachel 400
    In the first part of this series, I showed you how to customize the WordPress login screen by adding a custom logo and some custom styling. The next thing your users will see after they've logged in is the Dashboard, so in this tutorial you'll learn how to customize it by removing some of the existing metaboxes, moving some around, and adding some new ones.Read More…
  • Code
    Plugins
    A Better Forum List Widget for bbPressBbpress
    When bbPress was still a standalone installation, I had tried it out and wasn't really impressed. Things were clunky and it didn't always work the way it was supposed to. After languishing for a few years, Automattic decided to take bbpress and turn it into a plugin, improving the functionality leaps and bounds and making it a strong contender amongst other forum option for WordPress.Read More…