OWASP Top 10 & WordPress Security (Page 2)

Please note: this post is incomplete & pending review.

A1 – Injections

“Injection occurs when untrusted data is sent to an interpreter as part of a command or query.”

An injection can happen when an malicious data, is put into normal data. This data if correctly designed, can manipulate the apps, commands, or queries to do something unattended and malicious – like authentic an invalid user, add scripts to a database record, delete tables, append data to files, export data, etc. A simple example of this is if you had a MySQL query in PHP that takes in user data and creates a query:

$q = "SELECT * FROM users WHERE user='{$_POST['user']}' AND pass='".md5($_POST['pass'])."';";

In the above example $_POST['pass'] is actually useless to the attacker for an injection, the md5() function will turn any string into a 32-character hexadecimal number. However $_POST['user'] is the big vulnerability, the value is unverified, unsanitized, and unescaped. This query could be compromised with a user value as small and simple as user=1' OR 1 LIMIT 1; --. Submitting that value would manipulate the query and turns it into:

SELECT * FROM users WHERE user='1' OR 1 LIMIT 1; -- AND pass='';

This injected code creates an OR statement for our user select of 1, which is true, then selects the first entry with LIMIT 1. This method completely bypasses the password by commenting it out. The attacker could be successfully authenticated by simple returning the first result of the users table. This is an extremely easy and popular method of attack, it’s number one for a reason.

On the defence side, preventing it is easy. The data needs to be validated, sanitized, and if used in a query escaped. For our example, a developer could of first verified that the user value string was what it should be, sanitize it of unnecessary characters, then wrap it in mysqi_real_escape_string() to escape the data. Something similar to:

// validate (assuming all usernames in the design had a 12char restriction)
if (strlen($_POST['user']) < 12) {
    return false; // pass back to trigger an error handler
}

// sanitize
$pUser = preg_replace( '/[^a-zA-Z0-9_-]/', '', $_POST['user']);

// escape
$user = mysqi_real_escape_string($pUser);

Now $user can safely be used in a query.


How do we defend against this in WordPress?

WordPress has an army of built in functions and methods to help validate, sanitize and escape data, for a wide range of things, all in the name of prevent injection.

To validate, WordPress has tons, like is_email() to check the validity of a email. The sanitize_*()  helper functions aid in sanitizing almost every type of data type, including usernames. For escaping, WordPress offers again a bunch of functions to escape code like esc_html(), and (in staying with our example), a bunch of methods in WordPress’s $wpdb class to properly escape your query and prevent injection.

Most notable for any type of query is prepare(). Using the prepare() method we can build our query in a sprintf()-like way that allows us to place-hold the data and define it’s type (%d integer, %f float, %s string) then include the values as arguments that the method will escaping and sanitization respective to the type. Here’s an example of escaping and cleaning user supplied data before a query:

global $wpdb;
$q = $wpdb->prepare(
  "SELECT * FROM users WHERE user = %s AND password = %s",
  $_POST['user'], 
  md5($_POST['password'])
);

Using prepare() the values are escaped and sanitized, injection cannot happen. If we set the user value to 1' OR 1 LIMIT 1; -- the method above would return us a safe query of:

SELECT * FROM users WHERE user = '1\' OR 1 LIMIT 1; --' AND pass = '5f4d...cf99'

The query and the attack attempt would fail. The hacker stoped. The escaping, the \', is the day saver here. That single quote has been escaped and now it become part of the value, instead of part of the query structure.

Which is completely safe, no chance of injection. Although the ->prepare() method exists, we can be more secure by using methods for each action specifically, like ->insert(), ->delete() and so on. The rule of thumb in ^4.0 releases of WordPress is:

Unsafe API methods (which require sanitising/escaping):

  • $wpdb->query()
  • $wpdb->get_var()
  • $wpdb->get_row()
  • $wpdb->get_col()
  • $wpdb->get_results()
  • $wpdb->replace()

Unsafe escaping (‘securing’) API methods:

Safe API methods:

  • $wpdb->insert()
  • $wpdb->update()
  • $wpdb->delete()



Take Aways:

  • When handling user supplied data, you should always consider it “dirty” and “clean” it by:
    • verifying it, argue it agaisnt what you expect it to be (an int, string length, character ex/inclusion.
    • sanitizing it, clean it to be what you expect
    • escaping it, prevent any HTML or queries or any behaviour like that
  • Code as if every user is malicious
  • Always use WordPress’s $wpdb class and safe methods therein instead of your own mysqi_* functions
  • Check to see if escape or sanitize functions already exist in WordPress instead of writing your own
  • I’ve focused primarily on user-supplied data as thats the most obvious access point, but third party data, APIs, and even data from your own database should always be verified, sanitized, and escaped before doing something as well.

Leave a Reply