Developing for WordPress? Keep your shit secure

If you are developing WordPress plugins (or themes) for distribution via, or for client projects, it should be a no-brainer that writing insecure code can lead to severe consequences.

Having your plugin pulled from the repository, seeing a loss in respect and end-user confidence, or even worse, seeing users fall victim to easily preventable attacks are all possibilities if plugin security is not taken seriously.

With this in mind you would think WP developers would take extra care to avoid common exploits, however the research seems to indicate that insecure code is still rife in plugins and themes.

Taking the whole WordPress environment into consideration (plugins, theme, host, end-user), of the 170,000 sites hacked in 2012 this infographic suggests that around 22% were due to plugin vulnerabilities and 29% themes.


A second  infographic puts forward the perfect storm scenario in which a combination of plugins, themes and legacy hosting providers are all to blame for hacks.

Screen Shot 2013-08-13 at 18.09.25
The perfect storm

Most shockingly back in June, Checkmarx, an application security company, published a study analysing the most popular WordPress plugins in the official WordPress plugin repository for security issues and found that:

That's a big Twinkie.
That’s a big Twinkie.

20% of the 50 most popular WordPress plugins are vulnerable to common Web attacks. This amounts to nearly 8 million downloads of vulnerable plugins.

One-fifth…and thats not including the less popular plugins.

Lets also not forget that It’s not just newbies to the platform at fault; even experienced developers sometimes make mistakes, heck, even I’ll admit to letting issues slip through the net on occasion. And even though this doesn’t mean all plugins are insecure nor receive patches (plugins get security patches all the time remember) it still highlights the fact that as developers we could do much better when it comes to security.

So what can we do? 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:

[php]define( WP_DEBUG, true );[/php]

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:

[php]define( ‘WP_DEBUG_LOG’, true );
define( ‘WP_DEBUG_DISPLAY’, false );[/php]

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.

[php]if ( ! defined( ‘ABSPATH’ ) ) exit; // Exit if accessed directly[/php]

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:

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

You would replace this with:

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

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:

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

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

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

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:

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

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.





18 responses to “Developing for WordPress? Keep your shit secure”

  1. solepixel avatar

    Wow, so many great points here. Definitely some new ones for me, but a lot of good ones to always remember. Please plugin developers, ALWAYS use WP_DEBUG = true!

    1. mikejolley avatar


  2. Ricard Torres avatar
    Ricard Torres

    Awesome, learned a few new things.

    Thanks Mike.

  3. OriginalEXE avatar

    It’s awesome to read this and know you are doing it all (at least in the few talest projects, that is 🙂 )

  4. Bart Pluijms avatar
    Bart Pluijms

    May thanks for this fantastic Post! I will use it as an overview while developping!

  5. samikeijonen avatar

    You know what, wp_nonce_url was exactly what I needed. Thanks for the new tip and great article.

  6. Mark Jaquith avatar

    Nice post! Do note that KSES functions shouldn’t be run on output on the front end… they’re too slow. If you need to allow *some* HTML, then use KSES on save and make DARN sure that there is no other way to get data into that storage location.

    1. mikejolley avatar

      Thanks Mark.

      > If you need to allow *some* HTML, then use KSES on save and make DARN sure that there is no other way to get data into that storage location.

      It’s probably unlikely, but you cannot gurantee some other script hasn’t messed with your db data.

      What would you use for a faster alternative to KSES? Just some strip_tags/preg_replace functions?

      1. Mark Jaquith avatar

        There is no faster-but-still-secure method in WordPress core. It’s an expensive thing because HTML is not regular. Regular expressions can’t parse HTML. If the normal avenues of injecting the data are protected, then you are good. If someone else has access through some other flaw, then they could just compromise posts or comments anyway, as core does KSES on save for those.

      2. Bryan Petty avatar
        Bryan Petty

        I still like the idea of always filtering for security on output, partially because maybe there’s changes in what tags could be safe depending on a number of factors, most importantly being the location that content is being published (RSS, email, etc). However, I also really believe in always saving *exactly* what the user entered in all cases. If they screwed up somewhere with their tags, I don’t want them losing 4 pages of their article because it was between filtered tags (in your case, that content was likely deleted before it even hit an autosave revision, ouch). They can just go back and edit that one line mistake.

        Regardless, the appropriate solution to this performance problem isn’t to sacrifice security or other features. The solution is to add in a caching layer.

      3. Mark Jaquith avatar

        I do agree with you in principle. The issue is that we usually don’t have access to a good caching layer that could be used at this scale. On client sites where you control the stack, you might be able to make a different decision.

  7. harishchouhan avatar

    Thanks for the Great Article Mike. I hope everyone follows it. I have seen plugins created by well known authors respected in the community show error when debug is set to true. The plugins are safe, etc. but they still show error when first activated. I don’t want to list those plugins here, but I hope people fix these errors instead of saying to ignore it because its just basic index undefined error.

  8. limecanvaswil avatar

    Awesome post. Thumbs up!!

  9. Ryan avatar

    Excellent post Mike. A lot of fantastic advice here.

    One thing, regarding TimThumb. While add_image_size() is certainly good, it is a long way off from the functionality of TimThumb (which has had its security flaws fixed). The benefit of a function like TimThumb is that it works on-the-fly, and not when the image is first uploaded. This is fantastic if you have need for a variety of different sized images (slider, thumbs, etc.) for different post types/content but don’t want every image you upload to be resized in a bunch of pre-defined ways (inflating your uploads directory for no reason).

  10. Chris avatar

    Excellent pointers and very handy for newbie WP users like me.

    Do you happen to have some pointers with regards to themes having insecure codes and how to prevent this codes from obtaining information that is being made from your WP site? Maybe like the theme developer is able to see your passwords or your customers passwords who are using the your site. Some codes that might transfer data from your WP theme to them. I mean this scenarios is possible right, especially if the theme developer have some ill intentions.This could be really handy for us who don’t know or only have a little knowledge with regards to PHP or coding, and can only afford to buy themes and cannot hire developers.

    Any advice is greatly appreciated.

  11. seymourod avatar

    Really nice recommendations, not the typical and really usefull. Thanks!!

    by the way you have a litte mistake:

    define( WP_DEBUG, true ); is missing the ‘

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.