WordPress Security – a plugin done right.

It’s rare that I open the source code for a random plugin and see every recommended security measure taken. When looking at Chris Boyd‘s plugin GeoLocation Plugin, I kept digging deeper and deeper and found he’d consistently covered everything. This plugin is a text book example of how to write a secure plugin.

Since he has so many techniques in here, this post wrote itself in my head. It was irresistible.

I strongly recommend you download the plugin and open geolocation.php to follow along

So, here are the security methods I saw him use, there may be more I didn’t catch, if so feel free to add them to the comments:

Use nonces on all input forms

Chances are your plugin will ask for information. A nonce is a security token that helps guarantee that you are getting real data from a real user. In function geolocation_inner_custom_box() Chris calls

echo '<input type="hidden" id="geolocation_nonce" name="geolocation_nonce" 
	value="' .     wp_create_nonce(plugin_basename(__FILE__) ) . '" />';

to add the nonce to his input form and then to check this on the processing side in function geolocation_save_postdata he calls:

  // Check authorization, permissions, autosave, etc
  if (!wp_verify_nonce($_POST['geolocation_nonce'], plugin_basename(__FILE__)))
    return $post_id;

This is very simple code. There’s almost no cost to adding nonces to form processing and you can use that exact code, just changing the name of the field. This will block most bots from abusing your plugin.

Verify the user’s permissions

The user_can functions go hand in hand with the nonces and they work together to block the same kind of attacks. If your plugin does something that just anyone off the street shouldn’t be able to do, verify that the user is allowed to do it. This check is just a little bit further into function geolocation_save_postdata:

  if('page' == $_POST['post_type'] ) {
    if(!current_user_can('edit_page', $post_id))
		return $post_id;
  } else {
    if(!current_user_can('edit_post', $post_id))
		return $post_id;
  }

This simple check to confirm that the user is allowed to edit a post before the geotagging information is added and ties the plugin directly into all of the security already built into WordPress. It is tremendously powerful.

Configure default values

In programming 101, you learn never to trust the computer to give you a clean value for an uninitialized variable. In web development, there are even more implications to this. Caching is one of those. If you call get_option and WordPress does not have a value, it will access the database to see if there is a value set. Until a value is stored in the database, WordPress has to keep looking for it on every page load. Anything you can do that avoids accessing the database files and quite possibly the hard drives where the information is stored, will make your site run faster. If a value is found, that value will be loaded from the cache automatically. Additionally, if you set your default values, you know what you are getting back. Chris handles this in function default_settings():

function default_settings() {
	if(get_option('geolocation_map_width') == '0')
		update_option('geolocation_map_width', '450');

	if(get_option('geolocation_map_height') == '0')
		update_option('geolocation_map_height', '200');

	if(get_option('geolocation_default_zoom') == '0')
		update_option('geolocation_default_zoom', '16');

	if(get_option('geolocation_map_position') == '0')
		update_option('geolocation_map_position', 'after');
}

Use the Settings API and let WordPress do all the work

WordPress 2.7 added a wonderful set of tools called the Settings API. Most plugins do not take advantage of this complex tool set when they could. Chris uses the register_setting API call in function register_settings to enumerate the type of data he wants in each field.

function register_settings() {
  register_setting( 'geolocation-settings-group', 
		'geolocation_map_width', 'intval' );
  register_setting( 'geolocation-settings-group', 
		'geolocation_map_height', 'intval' );
  register_setting( 'geolocation-settings-group', 
		'geolocation_default_zoom', 'intval' );
  register_setting( 'geolocation-settings-group', 
		'geolocation_map_position' );
  register_setting( 'geolocation-settings-group', 
		'geolocation_wp_pin');
}

As you can see he specifies that certain values must be integers. Chris doesn’t take full advantage of the API in this plugin. In fact, I’m not convinced that simply calling register_setting on its own does anything. It is meant to be used with other calls. Had he used them all WordPress could have handled all sorts of things for him automatically, including the nonce field and additional checks.

The settings API is POWERFUL but has a steep learning curve. Once you pass the learning curve your input form printing just becomes five lines:

<form action="options.php" method="post">
	<?php settings_fields('plugin_options'); ?>
	<?php do_settings_sections(__FILE__); ?>
	<input name="Submit" type="submit" 
		value="<?php esc_attr_e('Save Changes'); ?>" />
</form>

This topic is WAAAAAAY to big to cover here and I freely admit, I’m no expert on it. I recommend that you read more: A quick & dirty example or The Whole Shebang

Verify your data

The last two topics are related as the come from the same rule: ALL data that you get from ANY source must be verified. This is where many plugins fail. The assumption is that “if it is in the database, it must be clean” or “it’s from an RSS feed, I know the data is good” or “The variable has INT in the name so it obviously contains an integer” or any of a thousand other gotyas. The way to keep yourself safe from an assumption is to VERIFY.
I want to highlight just two examples of this in Chris’s code.

First, in function display_location, he uses a cast to ensure the data he gets is what he wants from post meta:

$public = (bool)get_post_meta($post->ID, 'geo_public', true);

Second a function clean_coordinates uses regex to ensure the value he receives is what he wants:

$latitude = clean_coordinate($_POST['geolocation-latitude']);
[..]
function clean_coordinate($coordinate) {
	$pattern = '/^(\-)?(\d{1,3})\.(\d{1,15})/';
	preg_match($pattern, $coordinate, $matches);
	return $matches[0];
}

Escape all variable data you display

I won’t show specific examples of this as they are everywhere throughout the whole plugin, but this is one of the most important steps you can take to make your plugin secure. It is also one of the areas that has has the most focus in the WordPress core over the years.

If you are displaying a value that isn’t hard coded in your source, you need to first feed it through one of these functions (or something similar):

esc_attr()	Cleans HTML attributes for use on screen
esc_html()	Prepares complete blocks of HTML code
esc_js()	Special javascript handling of quotes and EOL characters
esc_sql()	Escapes data for use in a query
esc_url()	Returns a valid URL if possible
esc_url_raw()	Prepares an URL to be inserted in a database

This applies to values you are putting into javascript, urls you are displaying on the screen or using for includes, and data used to build image references. All this information must be sanitized. Visit the codex for more information on data validation.

Bonus Tip

If you’ve made it this far in the post, you get one more tip for free. DON’T DO IT ALL YOURSELF. I’d be willing to wager that this plugin was not ‘born’ with all of these security techniques in place. Though this plugin is attributed to Chris in the repository, he lists multiple authors for the plugin. You don’t HAVE to do all of this by yourself. Once your plugin is done, ask people to review it. I can pretty much guarantee that someone will find something. That’s just how it works. But won’t you feel much better if your friend catches the problem before it is discovered because your plugin added an avenue of attack to every site it’s been installed on? Someone will be glad to review it for you it. That’s part of what is great about the WordPress community: people love to help.

Summation

Website security will forever be an on going battle. It is a big, complicated subject with nuances you can miss easily. However, WordPress has been around long enough to have build up quite a collection of tools you have at your disposal as a plugin author. If use the tools WordPress provides, following Chris’s example outlined here, you can rest easy at night knowing you’ve done your job well.

Three helpful additions to your .bashrc

I just made a change to my .bashrc file and I thought I would share the tip. All of this is pretty basic stuff, but if you don’t customize your Linux logins, this would be a good place to start.

For Microsoft people who don’t know, .bashrc is in some ways like a combined config.sys and autoexec.bat file. If you don’t know what an autoexec.bat file is, you totally missed the 80s dudes…

In a *nix environment, the rc at the end of a file name typically means that it is a “run control” file. Run Control files execute when a program starts. In this case, the program is bash – the command line interpreter/shell. Other programs look for rc files too. Because of this, you could have bunches of them in your home directory. The . at the front of the file name indicates that they are hidden from a normal directory listing. This way they don’t clutter up your home.

I have lots of neat things in my .bashrc file that add functionality to my default CLI. I’ll be sharing just three of those with you now.

The first is an alias: ebrc. When I type ebrc and press enter, I’m taken immediately into an editor with my .bashrc file open. You can think of an alias as a single line shortcut. It looks like this:

alias ebrc='vi ~/.bashrc'

As you can see, it just says “when I type ‘ebrc’, treat it like I really typed ‘vi ~/.bashrc'”.

The second thing I used tonight was the alias brc:

alias brc='. ~/.bashrc'

That executes the .bashrc file again, so that all of the changes I’d just made are loaded.

You might ask “Can’t you just type all that out? You’re not saving much time.” Go ahead.. ask. I’ll wait…

OK. The answer is Yes. So, it is important that you don’t go overboard on this stuff. If you use aliases too much, you’ll lose your familiarity with *nix and the skills to do your work on any other server. So proceed with caution. This stuff can be addictive and detrimental to your guru health.

Now with those two helper aliases in hand, I added the function I really wanted to include: ‘upskel’. It takes a task I might otherwise put off and allows it to be completed in 7 keypresses. This is the perfect use case for a .bashrc function.

‘upskel’ takes the latest version of WordPress and places it into the cpanel skeleton directory that is used as the base for every new account created on my hosting service eHermits, Inc.. So, every time an update comes out for WordPress, I can spend 5 seconds to grab the latest and all new accounts I create will be safe and updated.

Unlike an alias, this is done through a function call. Functions allow the use of multiple lines and variables. Here is the call I just added:

function upskel()
{
  cd /root/cpanel3-skel
  rm -R public_html
  rm latest.zip*
  wget http://wordpress.org/latest.zip
  unzip latest.zip
  mv wordpress public_html
}

Technically I probably could have done that as an alias but a function is much easier to read with multiple lines involved.

As a bonus, here is a function that takes a variable:

function  ewpc()
{
  cd /home/$1*/
  pwd
  sudo vi ./public_html/wp-config.php
}

Can you tell me what it does?

WordPress DB Hacks: Determining the ID of a category parents

When manipulating WordPress databases for exports and merges, sometimes it is helpful to get a list of all of the parents for the categories your posts are in.

For the import I am working on right now.  The category list is being flattened from 30 categories down to 5 categories. The blog has just over 75 thousand posts in it and doing the conversion manually would be a rather arduous process to say the least.

So, I came up with this little query to give me the parent category for each of the child categories on that site.

It’s fairly simple but I’ve had to write it several times now and thought maybe someone else might need it sometime too:

SELECT `tt1`.`term_taxonomy_id` as 'Child Tax ID', `t1`.`term_id` as 'Child Term ID', `t1`.`name` as 'Child Name', `tt2`.`term_taxonomy_id` as 'Parent Tax ID', t2.`term_id` as 'Parent Term ID', `t2`.`name` as 'Parent Name' FROM `wp_term_taxonomy` `tt1`, `wp_term_taxonomy` `tt2`, `wp_terms` `t1`, `wp_terms` `t2` where `tt1`.`term_id` = `t1`.`term_id` and `tt1`.`parent` = `t2`.`term_id` and `tt1`.`taxonomy` = 'category' and `tt2`.`term_id` = `t2`.`term_id` and `tt2`.taxonomy = 'category';

Now I can just grab it instead of rewriting it again next time.  After all, this is The Code Cave…

PS – If you just want to see the top level categories, here’s how:

SELECT `tt1`.`term_taxonomy_id`, `t1`.`term_id`, `t1`.`name` FROM `wp_term_taxonomy` `tt1`, `wp_terms` `t1`where `tt1`.`term_id` = `t1`.`term_id` and `tt1`.`taxonomy` = 'category' and `tt1`.`parent`=0

Why does Facebook keep telling me my email address is broken

Periodically I would get messages from Twitter and Facebook telling me that my email address is invalid. I would just hit reconfirm and it would work fine for a while.

When a client came to me and said he was getting the same messages, it became important to dig into it. What did I find? The Spam Cop is to blame.

Continue reading Why does Facebook keep telling me my email address is broken

Using find to copy specific files on Linux

I was faced with a weird copy command I wanted to do today; so I thought I would share.

How do you copy files in a directory tree to another directory?

I wanted to copy all of the mp3 files in a directory tree over to one specific target directory.

Initially I thought the command

cp -r *.mp3 /target/directory/

would work, but even though it specifies the –recursive option, it does not iterate the subdirectory looking for the mp3 files.

So I resorted to my every faithful companion: find -exec. It seems like this is one of the most useful tools in Linux. In this case, here is the command line I used:

find . -type f -name ‘*.mp3’ -exec cp {} /targetdirectory/ \;