Over the past weekend, I had the pleasure of attending WordCamp Minneapolis 2015 and it was a blast. You can check out my twitter feed (@awebdevguy) for some pictures of all the swag I scored. I basically got a new wardrobe from it, so tickets are definitely worth it.
But anyways, back to what this post is actually about.
Plugins are great. WordPress is great. Sometimes though, developers make mistakes. If you’ve followed my blog at all, you’d know that I just created a plugin recently, and the entire time, all I could think about was security. Recently, some big security issues have come up in WordPress and some big name plugins (Yoast SEO, Gravity Forms, etc). The thing that’s scary about this, is that these guys are the pros. Even the pros make mistakes, so how likely is it for an amateur developer like me to slip up? Probably much more. So while at WordCamp, the very first talk that caught my eye was from John Havlik titled Writing (More) Secure Plugins. Obviously, I would like to know how to do that. Every WordPress developer should, and John does an excellent job of explaining not only why you should care, but how you can remedy the most common issues of security. His presentation can be found here so definitely give that a glance.
WordPress Is A Big Fat Target
That’s one of the problems of being king. It’s not that WordPress is inherently insecure. It’s just that it has such a large market share. Think of it like the conversation that inevitably comes up when discussing PCs vs Macs. Someone always says “Macs don’t get viruses.” which is blatantly false. They are just as prone to viruses as PCs but PCs just make for a bigger target. WordPress is the same way. It is estimated to make up ~23.8% of all sites on the web! Why wouldn’t you target that as a hacker?
And since there are so many WordPress sites, it’s not uncommon to find poorly developed plugins and themes. That’s why we, as developers, have a responsibility to make our plugins more secure. It isn’t entirely the developers fault though. PHP has to take some of the blame. PHP is very dynamic and weakly typed. Variables can be created on the fly, and you can’t really prevent a variable from suddenly becoming a string. Strings open us up to vulnerabilities with our HTML output, form input, and database queries.
XSS (cross-site scripting) is probably one of the most common and easy ways to get into a site. It looks like this:
<a href="<?php echo $_SERVER['REQUEST_URI'];?>"><?php echo $title;?></a>
The issue here is the $_SERVER request being echo’d out immediately. If someone were to discover this loophole, they could drop whatever code the wanted in the URL like so:
Now if someone really knows their way around WordPress or your site, they could inject any number of things there, and execute their own code on your server. So how do you prevent this? You almost have to stop anytime you’re accepting input from the client, and make sure it’s validated. Check that it lands between an expected range, that it is a certain type, and only allow specific characters. This will all help protect your plugin. John suggests that you sanitize and validate your inputs as early as possible. Using functions like intval(), absint(), and isset() will help against invalid types and values. Functions like sanitize_email(), sanitize_text_field(), and sanitize_file_name() will clean your inputs, but be warned, they might change values that your users have submitted. Be sure to check out John’s presentation for more examples.
SQLi (SQL injection) is nasty. We’re talking, all of your posts, comments, data, tables, etc. gone in one fell swoop. If you’re developing a WordPress plugin, use the WordPress API. The functionality is there, and most of the leg work is already done for you. Plus, it’s more secure. If you do have to access the database directly, WordPress has you covered there too. By using the $wpdb object, you get access to $wpdb::insert(), $wpdb::update(), $wpdb::delete(), and $wpdb::replace(). Those should be able to handle just about everything database related. If you are writing SQL, then use $wpdb::prepare() to make sure your query is safe to run.
When it comes to getting data back from your database, you can’t trust it. Data needs to be escaped as late as possible, cause who knows what our users entered in there. By using esc_html(), esc_attr(), esc_url(), and esc_js() we can prevent more problems.
<a href="<?php echo add_query_arg( 'foo', 'bar' ); ?>"><?php echo $title;?></a>
<a href="<?php echo esc_url( add_query_arg( 'foo', 'bar' ) ); ?>"> <?php echo esc_html( $title ); ?> </a>
Cross-Site Request Forgery is a combination of hacking and social engineering. Basically, by tricking someone who is already logged into your site to click on a malicious link, an attacker can gain that user’s access. That link could be embedded in a comment or an email. To prevent this from happening to your users, use a nonce. Nonces are one time use tokens that are used for protecting links that execute an action. For instance, if I want to create a link that deletes a user, I don’t want that link to be available to anyone. We want it protected so that it can’t be emailed off and clicked by another user. Nonce’s look like this:
wp_nonce_url( $actionurl, $action, $name )
$actionurl is the URL we want to protect, $action is our action name, and $name is our nonce name. At this point, I’d recommend checking out John’s presentation again, just because he does such a good job of explaining nonces. Also, read up on the WordPress Codex.
So to sum this up, don’t trust external inputs (including the database), validate early, and escape late. Those are some of the big things to keep in mind when developing a plugin. It’s also a good idea to never trust the contents of $_SERVER, as it could be manipulated by the client. And did you know is_admin() doesn’t check if the user is an admin? It checks to see if the dashboard or administration panel is attempting to display. So don’t try locking your admin specific features down with that. Aside from that, stay away from the PHP functions eval() and assert(). You’re just asking for trouble by using those.