If you develop WordPress plugins professionally or as a side hustle, it is important to recognize security issues. With WordPress powering large swaths of the internet, code security should be a vital part of your development.
In this light, I wanted to share several key areas of security that every WordPress developer should be mindful of during their development of code projects.
Don't Let Direct Access Occur
While many web hosts are built around allowing access to files that reside on the server, we shouldn't allow our plugin files to be accessed directly. We need to make sure that its only "WordPress itself" that is accessing the plugin files.
The best way to handle this is to perform a conditional check at the top of the plugin files that makes sure the file isn't being accessed from outside WordPress.
Escape the Data Anytime Output Happens
It is very important that any time we are fetching data out of the WordPress database that the data is clean and valid and isn't going to cause harm due to unexpected output results. This security aspect goes hand-in-hand with input sanitization (talked about next). So that if we are diligent in sanitizing the input and escaping the output we can prevent vulnerabilities like XSS attacks.
WordPress is a great codebase to develop on top of because of the many functions that exist to assist in the escaping of data output.
- wp_kses() and wp_kses_post() will strip out any untrusted HTML that might exist in the outputted data.
- esc_attr() will encode the outputted text for use in HTML attributes.
- esc_html() will encode the brackets/quotes/etc when outputting HTML data.
- esc_textarea() will encode the text that is used inside textarea HTML elements.
- esc_url() will properly encode URLs and reject invalid URLs.
- esc_js() will escape and properly encode text string characters being echoed for Javascript.
If the heavy lifting of creating escaped and valid output data is already done with functions listed above, there is no excuse for not taking advantage of these tools.
A quick example of functionality...
Sanitize Everything Being Inputted to the Site
There is a saying that any time you allow a user to input whatever they want into a form... they will do exactly that. If we don't sanitize inputs then we are opening our site up to XSS attacks by allowing others to manipulate posted data whether on purpose or by accident. Therefore, we must sanitize all data that is gathered from $_GET, $_POST, and $_REQUEST.
WordPress has a number of sanitization functions that should be used by all developers. You can see the list here in addition to the popular ones below:
- sanitize_title() - Makes sure that HTML and PHP tags are stripped out of the title. (Note: this can be modified.)
- is_email() - Makes sure the email is valid or it returns as false.
- sanitize_text_field() - Checks to make sure its valid UTF-8, converts single characters (<) to entities, strips out all the tags, removes line breaks, tabs, and extra whitespace, and even strips octets.
Always Always Always Debug
WordPress is so large and constantly moving systems that its hard for everyone to keep up with all the deprecation notices and changes in code. Plus, I don't think I've met a single developer that has never done a "typo" or forgotten a semi-colon or just plain fell asleep during the middle of a coding marathon. WordPress provides a debugging environment by simply adding the following line to your wp-config.php file.
define( 'WP_DEBUG', true );
But, you will notice that when you turn on debugging it will also reveal server information like file paths and other bits of knowledge that can be used by parties looking to do harm to your server/site. We can hide the display of the notifications, warnings, and errors that WP_DEBUG provides by adding an additional line to the wp-config.php.
define( 'WP_DEBUG_DISPLAY', false );
Nonce is Your Friend
One vector of attack bad actors attempt on many websites are CSRF (Cross Site Request Forgeries) that trick site request into doing a function that wasn't intended. The way to combat this issue is using a nonce (Number used once). A nonce is a unique token that gets generated at the beginning of each action and timestamped so that an action can be verified both against the action request and the timestamp.
WordPress provides nonce functions like wp_nonce_url() and wp_nonce_field() that can be used on any type of form input field. We can also verify that the nonce is valid and thereby accepting the input by using the wp_verify_nonce() function.
CURL Up and Die
If you are developing inside WordPress there is really no reason to use PHP CURL to fetching remote data because WordPress has two great functions called wp_safe_remote_get() and wp_safe_remote_post(). The best part is that not only do these functions take care of all the encoding of the data, but they also have built-in fallbacks that CURL does not.
An example of using wp_safe_remote_get() function:
Prepare Your Database Query
WordPress provides a database abstraction class ($wpdb) that is meant to reduce risks introduced by SQL Injection attacks. The $wpdb class has insert() and update() methods that already escape and safely insert data into the database.
But what about getting things OUT of the database. Most developers who are used to working with MySQL databases understand the complexity of the query() function. So it should be a relief to all to know that WordPress provides a method of pre-preparing the query to ensure that the query is safe and escapes the variables.
An example method of using the query and the prepare capabilities together:
Security is Your Responsibility
Yes, the above areas of responsible security are meant for developers. But they should also be understood by users of WordPress. When we begin to grow beyond simply installing a plugin or theme to starting to customizing a site, it is important that we all take security seriously. Nobody is perfect, but at least we can begin working towards getting into better habits of escaping and sanitizing data. Or becoming more diligent in preventing unauthorized access by using built-in conditional logic statements in WordPress like current_user_can().
Do you have a security "pet peeve"? What areas of security do you find yourself wrestling with? When was the last time you turned on WP_DEBUG on one of your sites?