How to Create Security for Your Own WordPress Plugin

One of the benefits of WordPress is its flexibility and the ease with which you can modify it. The open source environment offers many opportunities for new or experienced developers to create solutions to issues and invent creative fixes for common problems.

However, it also creates easy opportunities for people to look for security holes in existing source code. This tutorial will describe how to create security for your WordPress plugin to spot and repair areas that are vulnerable.

How to Create Security for Your Own WordPress Plugin


Creating a WordPress plugin is fairly simple. All you need is a rudimentary knowledge of PHP coding and a basic understanding of the WordPress administrative panel and file structure. It doesn't hurt to be familiar with MySQL syntax either.

Once you have created and tested your plugin, you can keep it for your own use, or you can register your plugin and make it available for others. These guidelines will give you a basic foundation for creating a WP security plugin.

How to Get Started

The first thing you need to do before you create your security plugin is to find an original file name for it. The file name should have have the following characteristics:

  • It should have a name that is identifiable to the plugin's function.
  • It should be unique.

The name can have multiple words in it. To check for the uniqueness of your plugin name, search the repository of existing WordPress plugins. Performing a web search wouldn't hurt either, especially if you plan to distribute your plugin.

For example: secureme.php

Next you must create a PHP file for your plugin. This is essential in order for WordPress to process your file. When you activate your plugin, it is the name of your plugin's php file that you will look for in the admin panel.

This is the base code for the PHP header for a plugin. It tells the WordPress management system that the plugin is in existence and to activate, load, and run it. Without this information, your plugin won't work:

<?php
/*
Plugin Name: secureme.php
Plugin URI: http://www.yourpluginurlhere.com/
Version: Version number
Author: Your Name
Description: A brief description of your plugin and what it does.
*/
?>

This is the bare minimum you need for the plugin information file.

You should include licensing information stating that the plugin is licensed by WordPress or that it is compatible with WordPress GPL2 licensing. This is a standard template for that information:

<?php
/* Copyright YEAR PLUGIN_AUTHOR_NAME (email : PLUGIN AUTHOR EMAIL)

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2, as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
?>

Creating Your Security Plugin

Hooks, Filters, And Actions

Now that you have finished the preliminaries, its time to create the plugin itself. You will need to add hooks to tie your plugin to specific WordPress events. Hooks are necessary to override any WordPress default functions that run counter to the current functions of your plugin. WP runs systematic checks to see if any plugin functions are scheduled to run at a given time, and if so, those are the functions that are run.

There are two different types of hooks, filter hooks and action hooks. An action hook enables modification of onscreen data or data that is stored in databases. This includes adding or removing headers and footers and similar actions using the function wp_generator.

Some examples of action hooks are:

  • init
  • get_header
  • create_category

A filter is a hook that is triggered before rendering text or sending data. For instance a the_content filter hook could be activated by a filter called change_capital_T to look for and alter any instances where the text indicated needs to be changed from a lower-case to a capital 'T'.

To view how to register filters and actions and find available WP hooks, you can go to the WordPress API page and learn more.

Functions

You will want to create a style sheet so that you can add CSS files to your WordPress page safely, and a menu to enable the user to open the plugin. Since this is a security plugin, only the page administrator should have access to it.

Template for style sheet:

add_action('admin_enqueue_scripts','secureme_styles');

function secureme_styles(){
    wp_enqueue_style('secureme_style', plugins_url('/css/secureme.css', __FILE__));
}

Template for menu page:

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('Secureme', 'Secureme', 'edit_pages', 'secureme-admin-page', 'secureme_main', WP_PLUGIN_URL.'/secureme/img/secureme.png');
    }
}

Any of these permissions can be changed at any time.

Metaboxes

You should add a couple of metaboxes that will display information for the server or system and to run a basic security check. The other metabox is meant to limit access to your admin panel and block access to all but admin-defined IP addresses. A metabox will provide a consistent appearance with WP admin themes.

Information and Security Check Template:

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

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

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

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

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

Template to Check for Admin Panel:

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 }
}

To limit IP access, simply create an .htaccess file to add your wp_admin directory that blocks out any IP addresses not approved by the administrator.

Creating Main Plugin Functions

Keeping things separate and organized makes it easier to navigate, so it helps to create a sub-directory in your secureme.php directory to store your main plugin functions. These functions will allow you to run checks for security holes and lapses, which is what your plugin is all about.

1. This will initialize and show MySQL version and information, and run a check to make sure php is in safe mode in order to avoid conflicts with other plugins and functions:

function secureme_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');
}

2. A remote FTP can enter your php.ini file is the allow_url_fopen is set 'on.' Though this is necessary for allowing pingbacks, it makes your code vulnerable.

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

3. This function will run a check on memory and system limits, and suggest solutions to fix security issues and vulnerabilities:

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');
}

You will receive a report that looks something like this, listing the strength or vulnerability of each element:

<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>

4. The next step is to back up and rename the default wp_ tables; you can use the $wpdb->prefix variable. Run the the safe_check_table_prefix and safe_errorsoff functions to check their security.

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 />';
}

5. This next function will run a check to make sure that the remove_function filter that was added earlier is running properly:

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

function secureme_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>';
}

6. Next create a plugin function that will check the vulnerability of your wp-config.php file.

function secureme_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;
    }
}

7. Next add your newly-created function file to your main secureme.php file

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

Rendering

The metaboxes created earlier should be added to your main secureme.php file using the safe_main() function.

function safe_main() {

add_meta_box("secureme_box_1", 'Basic Checks', "secureme_meta_box", "box1");
add_meta_box("secureme_box_2", 'System Information', "secureme_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>';
}

Testing and Debugging

The final step in the process is to run your plugin and fix any errors. You can add define('WP_DEBUG', true) to the wp-config.php file, or use this debugging code:

/**
* This will log all errors notices and warnings to a file called
* debug.log in wp-content only when WP_DEBUG is true
*/

define('WP_DEBUG', true); // false
if (WP_DEBUG) {
    define('WP_DEBUG_LOG', true);
    define('WP_DEBUG_DISPLAY', false);
    @ini_set('display_errors',0);
}

Fix each error that is found separately, and then continue to run debug until they have all been fixed.

Plugin Development Best Practices And Tips

  • To insure conformity to WP standards, please refer to the WP Coding and Inline Documentation Standards pages at WordPress.org.
  • Don't overload databases and serves with heavy code. Use only what you need and keep it light, tight, and efficient.
  • Before you begin creating your plugin, run a search for similar plugins, and strive to create something that is different than what's already out there.
  • Proper planning and organization are a must. A plugin is only as good as the attention that went into creating it.
  • When coding your plugin, you should create a prefix; this could be the name of your company, your initials, or anything else that distinguishes your plugin as yours and separates your code from pre-defined WordPress functions, other plugins, and WP themes. In our sample security plug, this would be laur_secureme.php.
  • Visit WordPress.org to submit your plugin to the WordPress Directory.

More Resources

Conclusion

The information in this tutorial will give you the basics for creating a plugin securely. If you have any problems, there is a whole community of forums available where you can find answers, exchange ideas and tips, and meet other developers.

WordPress is a good place to start; you can find tips and news on the latest updates, features, and fixes for any issues you might encounter.

How do you enhance your security in your WordPress Plugin? Please share with us in the comments below.

Opinions expressed in this article are those of the author and not necessarily those of Onextrapixel.