secure WordPress Plugin theme Development

There are several easy to implement rules you can follow to make your code more secure which I’ll briefly explain in this post.

1. Always develop with debugging ON

Deprecated notices, warnings, fatal errors – you don’t want these to be triggered by your plugin. If a server has error reporting turned on, these errors can reveal server information such as file paths with which an attacker can better plan attacks on your server.

Turning on debug mode will cause these errors to be visible allowing you to patch them accordingly. Add the following into your wp-config.php file to enable debugging:

1 define( WP_DEBUG, true );

In a production environment you’ll want these hidden to end-users but you may want them logged so you can at least track them behind the scenes. In this case you can also add:

1 define( ‘WP_DEBUG_LOG’, true );
2 define( ‘WP_DEBUG_DISPLAY’, false );

This will log them inside the wp-content/debug.log file.

2. Prevent direct access to your files

Most hosts allow direct access to files on your server, including those belonging to your plugin. Directly accessing plugin files will in most cases cause PHP errors which, like rule #1, will also lead to disclosure of your WordPress install path.

To avoid these errors you can add a simple ABSPATH check which terminates the script if accessed outside of WordPress.

1 if ( ! defined( ‘ABSPATH’ ) ) exit; // Exit if accessed directly

3. Sanitize all the things

Never trust user input, even that provided by admin users, because:

  1. you have no way of knowing the user is whom they say they are
  2. other scripts can manipulate posted data.

If you don’t do this you can be vulnerable to XSS attacks in which an attacker injects malicious code into your inputs and posts it to your server. You must sanitize all data collected from $_GET, $_POST and $_REQUEST to avoid this.

A favourite function of mine is sanitize_text_field. Not only does this function remove and encode invalid, possibly dangerous code, it also removes white space, tags and line breaks with minimum effort on your part.

There are several other more specific sanitization functions you can make use of too such as sanitize_title and sanitize_email. See the full list here.

In addition to sanitization you should also validate input i.e. are the values you receive from a user what you were expecting.

For example, if you have an email field in a form, after sanitizing the value, validate thats it actually is an email address using the is_email() function, otherwise reject the request.

4. Escape when you output

When you output any data from your database or from user input its always wise to escape it so that you are sure the output data is clean and valid – like input sanitization, this also helps prevent XSS vulnerabilitiesWordPress provides several functions to make this a doddle, including:

  • wp_kses and wp_kses_post which strip out untrusted HTML.
  • esc_js which escapes and correctly encodes characters in text strings which you intend to echo for JavaScript.
  • esc_textarea which encodes text for use inside textarea elements.
  • esc_url which correctly encodes URLs and rejects invalid URLs.
  • esc_html which encodes < > & " ' when outputting HTML.
  • esc_attr which encodes text like esc_html for use in HTML attributes.

For example, if your plugin output some HTML like this:

1 <a href=”<?php echo $url; ?>”><?php echo $text; ?></a>

You would replace this with:

1 <a href=”<?php echo esc_url( $url ); ?>”><?php echo esc_html( $text ); ?></a>

It’s that easy :)

5. Nonce your forms and urls

Cross-site request forgeries (CSRF) are where an attacker can trick a user into performing actions against their will.

For example, say you have a link to delete a post from the database. I could (with malicious intent) trick you into clicking a link which deletes your posts without your consent.

Similarly, if you accidentally visited a delete URL a second time you could delete posts by accident. This is where nonces can help.

A nonce (Number used ONCE) is a unique token generated using an action name and a timestamp which you can use to verify a request. Nonces can be added to both forms (using wp_nonce_field()) and urls (using wp_nonce_url()).

As an example, take our delete link and nonce it:

1 <a href=”<?php echo wp_nonce_url( ‘delete.php?id=1’, ‘delete_link’ ); ?>”></a>

Now when this is processed we can verify the nonce is valid using wp_verify_nonce:

1 if ( ! wp_verify_nonce( $_GET[‘_wpnonce’], ‘delete_link’ ) )
2 die( ‘Security check’ );

6. $wpdb is your friend when it comes to database queries

If you are doing anything with the database, never interact with it directly – use $wpdb. $wpdb is WordPress’ database abstraction class and using it will help reduce the risk of SQL injection attacks.

$wpdb provides and insert() and update() method which you can use to safely insert and edit data in the database – it does the escaping for you.

If you are doing a query directly using the $wpdb->query() method, you can ensure your query is safe by using $wpdb->prepare() which escapes your variables. Example:

1 $wpdb->query( $wpdb->prepare( “DELETE FROM $wpdb->posts WHERE ID = %d;”, $post_id ) );

Aside from the benefit to security, $wpdb makes your job much easier, so use it.

7. Avoid CURL when posting remotely

This is a common bugbear of mine. Using CURL directly should, and can easily, be avoided by using WordPress’ WP_HTTP class and wrapper functions, wp_remote_get and wp_remote_post being the most obvious.

Not only do these functions take care of encoding data you are posting, they also offer fallbacks for when CURL is not available.

8. Prevent unauthorized access

If functionality inside your plugin gives access or allows modification of sensitive information, you need to prevent unauthorized users from breaking things.

To check if a user can perform an action you can use the handy current_user_can() function which returns whether or not a user has access to a given capability.

9. Use the tools available to you and keep things lean

WordPress comes bundled with a bunch of php and javascript libraries which you are free to use. As a general rule of thumb, don’t bundle something if you can use core to do your bidding.

Take TimThumb for example, a php based image resizing library. This was used by many plugins and themes to resize images on-the-fly. This was then found to include a fairly substantial vulnerability. As a result the plugins and themes bundling it were also affected.

These plugins could have instead used WordPress’ native image resizing functionality (add_image_size()) which would have saved bags of hurt. Moral of this story, use native WP functions where possible and keep 3rd party code (at least code you don’t fully understand) to a minimum.

So, are your plugins secure?

Do you follow the rules above? You should, and hopefully this article will spur you to do so in future. Missed anything? Let me know in the comments.

When you activate a plugin, you will see a deactivate link appear in the plugins admin page. But deactivating only stops the plugin from functioning. After deactivation, you then get a delete link. The delete link is the key to tidying up after yourself. It should really be called uninstall.

If you create a file named uninstall.php in the same directory as you plugin, the code in this file will be run when the user clicks delete.

  1. if(defined(‘WP_UNINSTALL_PLUGIN’) ){
  2.   //delete options, tables or anything else
  3. }

The above code checks for the WP_UNINSTALL_PLUGIN constant which WordPress sets when you hit delete. Then you can proceed to delete any options or custom tables that your plugin created when it was activated.