wp_plugin_security
Coding - PHP - Wordpress

WordPress Plugin Development, Lesson 4: Basic Security

This is part of an ongoing series for programmers interested in developing WordPress plugins with PHP. If you are just starting out with WordPress, and want some more basic tips, check out the previous lessons:

A friend recently asked me what blogging platform she should use to start a new blog. I suggested WordPress, not only because it’s the one I have the most experience with, but because it’s the most used web platform on the planet, with by far the most expansion options in terms of themes and plugins. But she answered: “Isn’t WordPress pretty bad on security?”

The answer is that, though no web platform is perfectly secure and as the industry leader, WordPress is certainly more often a target than others, the core software itself is pretty safe. The problems with WP security arise not so much with the core code as with the thousands of extensions. The same thing that makes the platform so attractive – its flexibility and range of plugins – can also be its biggest weakness. For better and worse, anyone with basic PHP skills can start building their own plugins, often without considering security issues. This article is meant to point out some of the most common security mistakes that WordPress plugin developers make, and how you can avoid them.

First, I’ll go through five big-picture concepts for securing a WordPress plugin, then we’ll go through an example of a simple plugin made more secure by using these tools.

1. Validate Inputs

Whenever you accept input from a user, you are opening a door into your database and files. Forms like “Contact Us” are one of the first places a hacker will look for vulnerabilities. Everything that a user can send to your site should be passed through one or both kinds of input filter: validation, which checks to make sure the input is the kind of data it should be, and sanitation, which cleans out any troublesome code that a user might place in the input fields.

2. Use $wpdb and Established Database Functions

One of the nice things about developing in the WordPress ecosystem is that database connections (and thus, to some extent, database security) are pretty much done for you. The $wpdb class is always available inside WordPress, but still, some developers start from scratch and use their own database connection, which is less secure. In many cases, they don’t even need a DB connection at all, because the action they need can be accessed by another of WordPress’ native functions.

3. Prevent Direct Access

The code in a WordPress plugin should only be accessible from within WordPress, but often, developers create plugins as if they were stand-alone applications, which allows a hacker to play with your plugin without even having to break in through WordPress security. We’ll use the ABSPATH variable to make sure that only a logged-in WordPress user can even see your plugin.

4. Sanitize and Escape Outputs

Just as we want to validate and sanitize inputs, a secure plugin escapes any output based on user generated content to make sure that no malicious code has slipped through. WordPress includes a set of functions called kses, the “ses” standing for “strips evil scripts”. In our example, we’ll use kses to make sure there is no code in our output.

5. Use Roles and Permissions to Limit User Access

Just as many developers mistakenly allow their code to be accessed from outside of WordPress, many also make the mistake of not limited which WordPress users can access higher functions of the plugin. Every layer of security you add will dissuade or defeat some number of potential hackers, and so this is just another one. Make sure that even if someone gains low-level access to your WordPress install, they still can’t access your plugin unless they’re you or another trusted user.

Example: Starting Code

This is a simple admin page plugin, which allows the user to change the name and admin email of the site:

<?php
 
/**
 * @package WP Plugin Dev Lesson 4 Plugin
 * @version 1.0
 */
/*
Plugin Name: WP Plugin Dev Lesson 4 Code
Plugin URI: 
Description: Plugin Example for WordPress
Author: Your Name Here
Version: 1.0
Author URI: 
*/
 
// Functions

function example_admin_menu_form() {
  echo '<h2>Example Plugin Admin Form</h2>';
  $formsubmit = $_POST['formsubmit'];
  if ($formsubmit) {
    $exblogname = $_POST['exblogname'];
    $exblogemail = $_POST['exblogemail'];
    echo 'You chose the blog name: '.$exblogname.
       ' and the admin email: '.$exblogemail.'<br />';

    $changename = mysql_query("UPDATE wp_options 
       SET option_value = '$exblogname' 
       WHERE option_name = 'blogname'");
    $changeemail = mysql_query("UPDATE wp_options 
       SET option_value = '$exblogemail' 
       WHERE option_name = 'admin_email'");

    echo 'Fields updated.<br /><br />';
  } 

  $exblogname = '';
  $exblogemail = '';
  if ($findbn = mysql_query("SELECT option_value 
       FROM wp_options 
       WHERE option_name = 'blogname'")) {
    $exblogname = mysql_result($findbn, 0);
  }
  if ($findbe = mysql_query("SELECT option_value 
       FROM wp_options 
       WHERE option_name = 'admin_email'")) {
    $exblogemail = mysql_result($findbe, 0);
  }

  echo '<form method="post"><input type="hidden" 
     name="formsubmit" value="1" />
       Blog Name: <input type="text" name="exblogname" 
     value="'.$exblogname.'"/><br />
	Blog Admin Email: <input type="text" 
     name="exblogemail" value="'.$exblogemail.'" /><br />
	<input type="submit" value="Update" /></form>';
}

function example_admin_menu() {

  add_menu_page('Code Example', 'Code Example', 'publish_posts', 
       'code-example', 'example_admin_menu_form', '', 3);

}
 
// Hooks
add_action('admin_menu', 'example_admin_menu');

?>

Now, this isn’t the most useful plugin. It essentially replicates part of the function of the ‘Settings’ page in WordPress. But as an example, it serves quite well, because it contains all five of those security holes listed above.

Before we move on, take a minute and try to spot every one of the security holes in our example. Think about how you might fix them before I show you the corrected code.

Ready?

Example: Corrected

Look at the comment lines to see where I made changes.

<?php
 
/**
 * @package WP Plugin Dev Lesson 4 Plugin
 * @version 1.0
 */
/*
Plugin Name: WP Plugin Dev Lesson 4 Code
Plugin URI: 
Description: Plugin Example for WordPress
Author: Your Name Here
Version: 1.0
Author URI: 
*/

// Prevent direct access to this file, outside of WordPress
if ( ! defined( 'ABSPATH' ) ) exit;
 
// Functions

function example_admin_menu_form() {
  echo '>h2>Example Plugin Admin Form>/h2>';
  $formsubmit = $_POST['formsubmit'];
  if ($formsubmit) {
    $exblogname = $_POST['exblogname'];
    $exblogemail = $_POST['exblogemail'];

    // Verify email field
    $admit = 1;
    if (!is_email($exblogemail)) { $admit = 0; }

    if ($admit == 1) {

      // Sanitize any HTML code from the blogname variable
      $exblogname = wp_kses($exblogname, array(), array());

      // Check to make sure current user has permission to update options
      if (current_user_can('manage_options')) {

        echo 'You chose the blog name: '.$exblogname.' and the admin email: '.$exblogemail.'<br />';

        // Remove the raw mySQL queries and replace with core WordPress update_option functions
        update_option('blogname', $exblogname);
        update_option('admin_email', $exblogemail);

        echo 'Fields updated.<br /><br />';
      } else { echo 'You do not have permission to make this change.<br /><br />'; }
    } else { echo 'There was a problem with one or more of your fields. Please try again.<br /><br />'; }
  } 

  $exblogname = '';
  $exblogemail = '';

  // Remove raw mySQL queries and replace with core WordPress get_option functions
  $exblogname = get_option('blogname');
  $exblogemail = get_option('admin_email');

  echo '<<form method="post"><input type="hidden" name="formsubmit" value="1" />
       Blog Name: <input type="text" name="exblogname" value="'.$exblogname.'"/><br />
	Blog Admin Email: <input type="text" name="exblogemail" value="'.$exblogemail.'" /><br />
	<input type="submit" value="Update" /></form>';
}

function example_admin_menu() {

  add_menu_page('Code Example', 'Code Example', 'publish_posts', 'code-example', 'example_admin_menu_form', '', 3);

}
 
// Hooks
add_action('admin_menu', 'example_admin_menu');

?>

Are You Done?

Not so fast. There is more to WP security than can be covered in one post, but these are five big steps to secure your plugin. If you plug these five holes, you’ll be ahead of a lot of developers, and cut off most of those out there that want to break your site or use it for their own purposes. Stay tuned for more on this important topic. For now, happy programming!

About the author

Ian Rose is a web developer, blogger, and writer living in Portland, OR. In his day job, he develops WordPress plugins and custom PHP solutions, focusing on nonprofit clients. By night, he attempts to write both fiction and nonfiction. Ian's site is Seaworthy Web Solutions

Share this post

Leave a Comment

Subscribe To Our Newsletter