Five Ways to Secure Your WordPress Plugins

Plugins allow us to easily modify, customize, and enhance the already amazing WordPress platform. They also allow us a way to share enhancements with those who aren’t able to write their own code. With this freedom and flexibility, it is our responsibility as developers to maintain the same level of care and security in our plugins that everyone expects of the WordPress platform.

I’d like to run through some basic security practices you should include in your plugin where necessary. This information is not just important for developers to know but can be invaluable to non-developers wanting to review plugins before installing and activating them on their sites.

Prevent direct access to PHP files

When WordPress loads it looks for all currently active plugins and loads the appropriate file from each active plugin’s directory. Because these PHP files are most likely located within the web server’s document root (ex: /srv/www/yourgroovydomain.com/wp-content/plugins/my-plugin/my-plugin.php with yourgroovydomain.com being the root directory), anyone can access them from a web browser or other program by going to http://yourgroovydomain.com/wp-content/plugins/my-plugin/my-plugin.php. This is bad.

We must account for the possibility that attackers may exploit this to harm our user’s website or their hosting provider’s server. Preventing this is very easy and should be the absolute first thing you add to the top of your PHP files — before any other code is executed.

We want to prevent public access to this file but still allow WordPress to load the file internally. The most common solution is to check for the existance of the ABSPATH constant defined by WordPress when your site is loaded. If the constant exists, then WordPress is requesting access to load this file. If the constant does not exist, someone is trying to directly access this file and we want to stop them immediately.

<?php defined( 'ABSPATH' ) or die();

Conduct capability checks

WordPress has six pre-defined roles, which give the site owner the ability to control what logged-in users can and cannot do within the site. These roles are: Super Admin, Administrator, Editor, Author, Contributor, and Subscriber. We need to make sure our plugin allows the site owner to retain this control.

WordPress provides us a handy little function to check whether the currently logged-in user has a specific role or capability: current_user_can().

if ( current_user_can( 'edit_users' ) ) {
    // The current user can edit other user's information
}

In the above example we check whether the currently logged-in user can modify other users’ information on the site before allowing the ability.

Guard against SQL Injections

If your plugin interacts with the database at all you must escape your SQL queries before the SQL query is executed to prevent SQL Injection attacks. SQL Injection is a very simple exploit attackers use to alter an existing SQL command to expose or override data, and could even be used to execute dangerous system level commands on the web host. Escaping your SQL queries is the solution but it’s very often overlooked by developers.

Here’s an example of code which is susceptible to SQL Injection:

$unsafe_variable = $_POST['comment'];
$wpdb->query( "INSERT INTO wp_comments (comment_content) VALUES ('$unsafe_variable')" );

Why is this bad? We are trusting the content of $_POST['comment'] regardless of what it actually contains! If you see this in a plugin you’ve installed, you are at risk of SQL Injection. Deactivate the plugin and contact the developer regarding this security risk. WordPress provides the $wpdb->prepare() function which allows us to easily escape our SQL queries. Here’s how the code above would look using $wpdb->prepare():

$sql = $wpdb->prepare(
    "INSERT INTO wp_comments (comment_content) VALUES (%s)",
    $_POST['comment']
);
$wpdb->query( $sql );

This code would be protected against SQL Injection. You can read more about protecting queries against SQL Injection attacks on the WordPress Codex.

Prevent Cross-site Scripting (XSS)

Cross-site Scripting is another form of injection attack. It occurs when an attacker is capable of injecting code into the output of a web page which is then executed by the visitor’s web browser. What we learned about SQL Injection is that it’s important to escape data before we save it to our database servers. However, some data — which is escaped and safe to save on our servers — can still be valid HTML, CSS, or JavaScript which could be unsafe to output to our visitor’s browser.

Now we need to sanitize any data we obtain from a user’s input before we output the data. If we don’t, that HTML, CSS, or JavaScript can alter our website and cause problems for our visitors. As you’ve probably guessed, WordPress provides several functions to make this easy.

Say we want to generate a link from data our user has input. Let’s make the text of that link the user’s name and have it point to their website’s url.

What you don’t want to do:

<a href="<?php echo $user_url; ?>"><?php echo $user_name; ?></a>

This is what you want to do:

<a href="<?php echo esc_url( $user_url ); ?>"><?php echo esc_html( $user_name ); ?></a>

Familiarize yourself with the sanitation functions WordPress provides and use them where appropriate.

Prevent Cross-site Request Forgery (CSRF)

Cross-site Request Forgery is an attack that tricks a user into submitting a malicious request. This can happen when a user is logged into a website and clicks a seemingly harmless link (which could have been injected via XSS). This link could cause the user to intentionally make a change to their account (they have authenticated access), and in doing so, they give another person access to it. This is just a single example but CSRF is a common and serious exploit we need to prevent.

To prevent a CSRF attack we need to ensure that an action is actually being performed by the authenticated user rather than an attacker. What we need is some sort of unique identifier which we can verify for a request. We will use a nonce (number used once) as this is a unique identifier.

WordPress provides us with a few functions to use nonces in our plugin. You can add a nonce token to a url using wp_nonce_url() and you can add one to a form using wp_nonce_field(). Anytime a user needs to make an authenticated request, whether by clicking a url or submitting a form, you need to generate a nonce for it. When you use the nonce functions provided by WordPress, a token will be generated on the server side. We will then verify the token’s existence in our plugin before executing the authenticated request.

Here’s a simple example of how to add a nonce to a form:

<form action="/submit-comment/" method="post">
    <input type="text" name="comment">
    <input type="submit">
    <?php wp_nonce_field( 'submit_comment' ); ?>
</form>

When a user fills out this form, the data is sent to the server along with the generated token. We then need to verify this request with the token:

$nonce = $_REQUEST['_wpnonce'];
if ( ! wp_verify_nonce( $nonce, 'submit_comment' ) ) {
    // nonce is invalid!
}

If the nonce is invalid we will halt execution of this malicious request. Why does this work? The nonce token is generated by the server, unique to each user, and is unkown to the attacker. This simple token prevents an attacker from forging a request.

In Closing

We’ve gone through a handful of ways plugins can be exploited by a hacker, but with each exploit WordPress provides a simple solution. Writing plugins for WordPress gives us so much freedom and flexibility on the platform and with this ability comes a responsibility to the community; a responsibility to develop secure plugins that the community can trust and use on their websites. And by doing so, we are making the web a better place.

About JR Tashjian

Follower of Christ, Freelance Software Engineer, Previously worked at Automattic and PacketTide, Motorcycle enthusiast, outdoorsman, addicted to fitness. I occasionally post about web development on jrtashjian.com. I talk a lot on Twitter and post things I've been reading. You can check out my code and contributions on Github. I like pretending to be a photographer and post some of my photos on Flickr. I've also been known to design a few things from time to time on Dribbble.
This entry was posted in Security and tagged , , . Bookmark the permalink.

One Response to Five Ways to Secure Your WordPress Plugins

  1. Pingback: Five Ways to Secure Your WordPress Plugins | JR Tashjian

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s