Editor’s Note: This is a guest post by Jack Lenox. Jack is a developer at Automattic and hails from the United Kingdom.
For just over a year now, I have been working on the WordPress.com VIP team at Automattic. I had been working at Automattic for the two years prior to this – and had been developing sites with PHP and WordPress for almost ten years prior to that. So you might imagine that I had a pretty good handle on developing stuff with WordPress.
And you would be wrong. Getting started with the VIP team was an eye-opening and occasionally terrifying learning experience, occasionally resulting in me thinking: “please excuse me for a moment while I go and fix some horrible vulnerability in all of my WordPress sites.”
Recently, I have cautiously found myself feeling slightly more comfortable with my position on the team. For some time, I have been wanting to document the most interesting and impactful things that I have learned in the past year.
As some readers may know, a significant part of a developer’s job on the VIP team is reviewing code. Thus, with us being at the start a new year, I have hereby compiled some of the most interesting best practices I have discovered as a list of New Year’s Resolutions:
1. Use strict comparison operators
One of the many quirks of PHP is that it enjoys juggling. In particular, it enjoys juggling types. This means that without explicit instruction, PHP doesn’t see a difference between a string of “string”, an integer of 0, and a boolean value of true
.
So for example this:
$var = 0;
if ( $var == 'safe_string' ) {
return true;
}
Will return true. I know, what?! The easy solution here is to simply use strict comparison operators.
So that’s ===
instead of ==
, and !==
instead of !=
. This pops up in a few other places too. By default the in_array()
function has its strict parameter set to false.
So:
in_array( 0, ['safe_value', 'another string'] );
Will return true. To fix this, simply pass a third parameter of true
.
While we’re here, there’s one other form of comparison we should be aware of, and that’s hash_equals()
. This provides a string comparison that prevents timing attacks.
While a relatively uncommon form of attack on the web, it’s worth being aware of a timing attack. What is it? Well, when PHP compares two strings, it compares them one character at a time.
So in the case of something like this:
$submitted_password = $_POST['password']; // For argument's sake, let's say it's "pa45word"
$password = "pa55word";
if ( $submitted password === $password ) {
go_forth();
}
PHP’s thought process in human terms is: Is the first character of each string p? Yes it is. Is the second character of each string a? Yes it is. And so on.
It will do this until it realizes that the third characters differ and at that point it will bail. Thus, with sophisticated timing software, a password can gradually be worked out by calculating how long the process is taking. If the process takes slightly longer with one character than it does with every other character, an attacker will know that they have worked out the first character.
Automated processes can keep doing this until the entire password is worked out. hash_equals()
will compare two values, but will not bail early if it detects a difference.
In conclusion, if you’re comparing sensitive values, use hash_equals()
!
2. Use Yoda condition checks, you must
The WordPress PHP Coding Standards suggest that you should: “always put the variable on the right side and put constants, literals or function calls on the left side.” Initially, this might just sound like a bit of pedantry, but it actually has a very practical application.
Consider how catastrophic the following typo could be:
if ( $session_authorized = true ) {
unleash_the_secrets();
}
Oh dear, instead of checking that $session_authorized
is true, I am instead assigning the value of true to that variable.
Now the secrets are being unleashed to whoever wants them. This could easily be missed when checking the code for bugs, even by a reviewer.
Now imagine if the first line was expressed as:
if ( true = $session_authorized ) {
Well, it doesn’t. We can’t assign a variable to the static boolean value of true
.
Hopefully it won’t take us too long to work out why our code is still broken, but the secrets remain safe. So we’re good!
3. ABE. A Always, B Be, E Escaping. Always Be Escaping. ALWAYS Be Escaping.
Not having a firm grasp of the concepts of validation, sanitization and escaping can make you a very dangerous developer indeed.
To the extent that libraries like React escape all output by default and to bypass this functionality, you have to use the attribute: dangerouslySetInnerHTML
Validation is checking that what your code is being passed is even vaguely what it’s expecting. So for instance, if we’re expecting an integer, we can use something like: $zipcode = intval( $_POST['my-zipcode'] )
The intval()
function returns its input as an integer and defaults to zero if the input was a non-numeric value. So while this won’t prevent our code from being passed zipcodes that aren’t valid, it does protect our code from being passed anything that isn’t a number.
Naturally, we could go a step further to see if the zipcode actually appears to be valid. For example, 1111111111111 is not a valid zip code, but intval()
doesn’t know that.
Fortunately, beyond integers, WordPress has a bunch of handy helper functions for almost every data type including my favourite: is_email().
Sanitization is cleaning input to make sure that it’s safe in the context where we want to use it. This prevents one of the most common forms of security vulnerability, an SQL injection attack.
We also sanitize to fix practical things, like checking for invalid UTF-8 characters. WordPress has a class of sanitize_*()
helper functions; here’s an example of how one looks in the wild:
$title = sanitize_text_field( $_POST['title'] );
update_post_meta( $post->ID, 'title', $title );
Therefore no matter what garbage we might have been passed in $_POST['title']
, it won’t cause any real problems.
Escaping is similar to sanitization, but instead it is cleaning what we’re sending out, rather than what we’re taking in. A major reason for doing this is to prevent another of the most common forms of security vulnerability, a Cross-site Scripting (or XSS) attack.
We want to clean our output to ensure we aren’t accidentally echoing out something very dangerous that we didn’t realize we were inadvertently storing in our database (or perhaps fetched from an API).
WordPress has a bunch of very useful helper functions here. Some common examples of these in the wild are:
<h4><?php echo esc_html( $title ); ?></h4>
<img alt="" src="<?php echo esc_url( $great_user_picture_url ); ?>" />
<ul class="<?php echo esc_attr( $stored_class ); ?>">
There is also wp_kses()
which can be used on everything that is expected to contain HTML, and will filter out elements that are not explicitly allowed.
As a general rule, the the_*()
and get_the_*()
theme functions are already escaped. However, the get_bloginfo()
function, for example, is not escaped.
For further information here, I highly recommend checking out the VIP team’s documentation on Validating, Sanitizing, and Escaping.
4. Stop trusting everything
Don’t trust user input. Don’t trust what’s in your database. Don’t trust any variables.
Treat every variable with contempt.
This way, even if, for example, someone sneaks some dodgy XSS code into your database, it’ll still get escaped on output and your site will be better protected.
5. Avoid inserting HTML directly into the document (when using JavaScript)
Doing something like this is dangerous because the data that we’re using could include many more DOM elements that dramatically alter the anticipated behavior of this code, and make it vulnerable to XSS attacks:
jQuery.ajax({
url: 'http://any-site.com/endpoint.json'
}).done( function( data ) {
var link = '<a href="' + data.url + '">' + data.title + '</a>';
jQuery( '#my-div' ).html( link );
});
Instead, we should programmatically create DOM nodes and append them to the DOM. So the above instead becomes this:
jQuery.ajax({
url: 'http://any-site.com/endpoint.json'
}).done( function( data ) {
var a = jQuery( '<a />' );
a.attr( 'href', data.url );
a.text( data.title );
jQuery( '#my-div' ).append( a );
});
This is how a library like React does things behind the scenes. You can read more about this in a wonderful post about preventing XSS attacks in JavaScript by my colleague, Nick Daugherty.
6. Review code
Have you ever reviewed a plugin before using it? I know, who’s got time for that right? I’ll tell you who: you.
I have come to realize that reviewing code is possibly one of the best exercises for improving as a developer. Even if you’re quite new to programming or development, and you still feel pretty green, you really should give it a go.
A great way to start is to review the next plugin you decide to use on your website. Before activating it, pop it open in your text editor of choice, and just spend some time scanning through it to understand what it does.
A method I like to use here is to interpret each line of the code in simple English. You can even say it loud if you like – assuming you’re not sitting in a café or co-working space where people might become worried about you.
You might be surprised at how often you find bugs and quirks in the code, or that the code isn’t conforming to the best practices outlined above. And if you discover issues, why not create a patch? Or if the plugin is on GitHub, create a pull request.
You can also review your own code. A great method for doing this is to never deploy code straight into production. Instead, leave it on the day you finish it, and review it line by line in the morning. This method is easiest to adopt if you’re using something like GitHub where you can create a pull request with the changes, then review the pull request yourself the next day before merging it.
In this vein, I highly recommend watching my colleague, Ryan Markel’s, fantastic talk on this topic from WordCamp US 2016.
7. Upgrade your tools (or at least use PHP_CodeSniffer)
There are lots of tools that help make web development easier, but if you’re doing a lot of WordPress development, the most valuable is probably PHPCodeSniffer. It reads your code and automatically reviews it for bugs and coding standards inconsistencies while you type.
It’s kind of like a spell checker, but for code. No matter how good your English is, you still use spell check right? So why wouldn’t you spell check your code?
Here’s a bonus for you: the WordPress VIP Coding Standards are available by default with the WordPress Coding Standards for PHPCodeSniffer. So with that, it’ll check if you’re following most of the above resolutions.
As you might imagine, using PHP_CodeSniffer also really helps highlight potential problems when you’re reviewing plugins and other people’s code.
8. Be curious
Far too often, I’m guilty of searching to try to find out what a particular WordPress function does, or scanning Stack Overflow to see if someone’s having the same problem as me.
I have historically had a bad habit of seeing much of what WordPress does as magic, and avoiding getting too deep in the inner workings. But actually, it can be very beneficial to find out answers for yourself, instead of trying to find others who have already done the work.
In essence, WordPress is quite simple. The code largely consists of functions taking arguments, and doing things with those arguments, and passing the results onto other functions taking arguments, and so on.
It doesn’t take much to start unpicking something, and working out exactly what’s happening behind the scenes. So next time you’re struggling with a function, try going straight to looking at what the function actually does.
Personally I find the WordPress GitHub repo that mirrors the core SVN repo to be a very useful way of doing this.
The WordPress strapline is that “code is poetry”, and for its flaws I find that on the most part, the WordPress codebase is very readable, if nothing else!
I’ll conclude by taking this opportunity to wish you a very happy and prosperous new year!
Note: Some of the above has been gleefully plagiarized from WordPress.com VIP’s Code Review documentation. It’s an Aladdin’s cave of useful advice, and I highly recommend working your way through it as and when you can.