Are Your Plugins Secure? Part 2: Permissions and Nonces
Published November 20th, 2012 under General
I originally authored this post on WP Realm, but I moved it back here after that website folded.
Following on from “Are your plugins secure?, this post is a simple breakdown on what to look for security-wise when auditing plugins. In the previous post I covered basic data santisation, however that sort of protection means diddly squat if a plugin allows a random member of the public is able to do do something only meant for you!
For WordPress plugins to allow user submitted inputs, it either needs to allow anyone to submit data, or it needs to perform some sort of authentication to prove that the user submitting the data is indeed who they say they are.
User permission
At the most basic level, a plugin must do a simple check to ensure that the user performing a specific action indeed has the appropriate rights to perform the action they are attempting. Usually this involves things like administration pages, which must ensure that the page is only displayed to administrators. Thankfully most plugins handle this side of things very well and it is rare to find plugins which don’t do such as basic check.
For most administration pages, the processing of form fields is handled internally by the WordPress options API, however many plugins and themes attempt to store their data manually by processing $_POST
variables. Sometimes this is for good reasons, but often it is because the plugin developer was unsure of how to handle the storing of the data, in this situation it is critically important to check that the data stored is handled in an appropriate manner.
Code such as the following will probably be okay since the administration page most likely have a user permission check on it.
https://gist.github.com/3926862
However, if you see something along the lines of the following in a place which is not part of an administration page, and does not have a clear check for user permission level, then beware! A malicious user could potential submit data to your site and inject code willy nilly.
https://gist.github.com/3926864
The standard way to manually perform user permissions checks is to use the current_user_can()
function. This does not involved a direct check for the user level, but checks what permissions level the user has. Administrators typically have the ability to manage options, and so “manage_options” is often used to check if a user is an administrator or not.
https://gist.github.com/3926865
User intention
Thankfully, user permissions are often handled correctly in WordPress plugins. Unfortunately, the same can not be said for the more abstract yet equally important issue of user intention. Just because the person initiating an action has the appropriate permissions, it is not necessary true that the user intended to initiate that action. Examples of this are when a legitimate, well-meaning administrator navigates to a malicious site which causes them to submit a form which points directly at their own site. This could cause the administrator to inadvertently submit form data which they never intended to submit to their own site. The results of this could be disastrous and need to be avoided at all costs.
Thankfully WordPress has a convenient built-in system for checking user intention called nonces. Mark Jaquith wrote an excellent article on this issue way back in 2006. Nonces add an extra input field with a set value to a form, or an extra query variable to a link. The plugin/theme is then able to check that the value of the nonce submitted matches the one stored in WordPress. Nonces are unique for each logged-in user, so it should not be possible for attackers to know the intended value of the nonce.
There are many different ways to implement nonces in WordPress. The most common routes for forms are using wp_nonce_field()
which adds a simple hidden nonce input field, or settings_fields()
.
https://gist.github.com/3926866
Links can be handed via the wp_nonce_url()
function as follows:
https://gist.github.com/3926867
There are also various ways of checking for the presence of nonce values on the backend. The most common route is via check_admin_referrer()
or if the settings API is used, then WordPress will handle the checking of nonces directly.
https://gist.github.com/3926869
AJAX requests can be handled server side via the check_ajax_referrer()
function.
https://gist.github.com/3926871
If you are unsure if nonces are implemented correctly, one of the easiest ways to check is to simply look at the generated HTML and check if any nonces are present. If there is a form, there should be a hidden form field which stores the nonce. A quick search for the text “nonce” is often sufficient to confirm the presence of nonces used in a form.
Conclusion
This is not a comprehensive overview of WordPress security. You cannot teach yourself how to perform a security audit on a WordPress plugin or theme by reading a couple of blog posts. I do hope that these tips will help guide you in the right direction to learn more and I hope I have broken down some of the more confusing aspects of WordPress security into something understandable. When I was attempting to learn these things myself I had awful difficulty finding easy to understand articles on the subject. Over the past year or so there have been more developers becoming aware of the complications involved in writing secure code for WordPress and thankfully this has resulted in much better documentation on the matter. Security is something which we much constantly strive to learn about to ensure that our own knowledge of the subject is ahead of that of the malicious attackers.
Paul says:
Perfectly timed post! Pippin just wrote about how he found a security flaw in one of his plugins and how he fixed it. It’s a nice case study to illustrate your points:
http://pippinsplugins.com/i-wrote-some-really-dangerous-code/
November 21, 2012 at 12:22 pm # //
Ryan Hellyer says:
I’d been chatting with Pippin about his plugin security issues around the time I wrote this article actually.
November 21, 2012 at 12:26 pm # //