WordPress Hacks: functions.php Backdoors

<- Return to Cybersecurity blog

WordPress Hacks: functions.php Backdoors

Introduction

I recently noticed that a number of our client sites had some suspicious code added to the top of the functions.php file for each of the installed themes. This is an investigation into this code and details of how to remove it.

I am making this information available in the hope that it will be a useful resource for anybody that suffers this attack on their own WordPress sites. I strongly believe that by making this information public I can help others to combat these types of attacks. So, I hope you find the following analysis useful!

Please note that I have used <wp_prefix> in all table names in this post. This represents the prefix that has been configured for the WordPress site and is usually "wp". This means that if you see a table referenced as <wp_prefix>_posts, by default this would be the wp_posts table.

Code examples

Detection of Infections

For each of the WordPress installations of the server, malicious code was added to the top of each functions.php file within the root directory of each installed theme. This file needs to be present in order for a theme to be recognised by WordPress and is executed during every page view, meaning that it's a good target for malicious code.

As an example, let's say the site had a theme called "MyTheme". After the attack, the malicious code would have been added to the wp-content/themes/MyTheme/functions.php file.

This code has a signature, which is that it uses a variable called wp_cd_code. This means that we can easily identify if a file is infected. If you are running a Unix-based OS, you can list all of the files that are affected by this attack by running the following command in the root directory of your server's web-root: -

find -iname '*.php' -print0 | xargs -0 egrep -in 'wp_cd_code'

This will search through all *.php files recursively for the string "wp_cd_code", which is the name given to the variable that stores the malicious code. The output will show the file path and the line number that matches the search string.

It is unclear as to how exactly this code got into all of the functions.php files on all sites on my server, but for one in particular the culprit happened to be a malicious plug-in that a client had installed. This plug-in was called woocommerce-direct-download, which contained a malicious file called woocp.php. This script contains some obfuscated PHP code which, when executed, performs the injection of the malicious code into all functions.php files for the site. I will refer to this script as the Code injector in the following analysis.

Code Analysis

Code injector

The code injector script performs the following steps: -

  1. Generates a hash for this particular site, which is defined as the MD5 sum of the site's HTTP host and the WordPress AUTH_SALT value, which is set in wp-config.php. The code used to generate this hash is md5($_SERVER['HTTP_HOST'] . AUTH_SALT).
  2. Replaces the {$PASSWORD} string in the Base64-encoded payload ($install_code).
  3. Creates two new database tables: <wp_prefix>_install_meta and <wp_prefix>_datalist.
  4. Searches the themes directory of the site for functions.php files.
  5. For each functions.php file, injects the modified payload (see step 2) into the file as long as it isn't already there.
  6. If there were no functions.php files that were already infected, reports the generated password for a host to a "control server" at http://apiword.press/q.php?host=<hostname>&password=<password_hash> (I also saw the use of o.php). This allows the attacker to build a nice database of hosts and their associated passwords which they can use to exploit each infected site. The response from this request are then written to a file called license.html in the site's root.
  7. Removes the code within the file containing the injector so as to hide its existence.

To summarise, the injector takes care of making sure that the malicious code is configured for each site, injected into a PHP script that will be run upon every request, and that the infection is reported back to the people that attacked the site in the first place. The attackers can then use the password that is generated to perform actions such as adding posts without having to login using the usual route.

One important thing to note here is that the MD5 hash (which is the password used to access this backdoor) is only salted using the WordPress AUTH_SALT key. This shows how important it is to generate you own keys for each WordPress site you operate! When WordPress is first installed, this key is set to a default value. This means that if we knew that a site is infected with this backdoor then we would also have a fairly good chance of guessing the password hash, meaning that we might also be able to use the backdoor!

An example of this script can be viewed here, and a decoded injector payload can be viewed here.

Malicious code in functions.php

The code that is generated and stored in functions.php checks for action and password variables passed in the HTTP request. If the password matches the hard-coded hash (generated by the code injector as above) then code then executes an action based on the contents of the action variable.

The actions that can be performed by this script mainly involve adding and modifying posts on the site. When the attacker executes the infected functions.php file they are able to inject arbitrary content into posts. This is of course a bad thing! The attacker is also able to create custom items in a <wp_prefix>_datalist table. Items in this table can be inserted or removed by the attacker and can have any content, as well as a specific url.

The possible values for the action variable are as follows: -

  • get_all_links: lists all published posts that have a DIV with an ID of wp_cd_code. For each post, the GUID (URL), post ID and contents of that DIV tag are displayed.
  • set_id_links: inserts or updates a DIV with an ID of wp_cd_code in a post specified by ID. If the DIV exists, the contents are updated with the data passed in the request.
  • create_page: based on a passed remove_page or content values, either removes or inserts an item in the <wp_prefix>_datalist table, which is a table created by this exploit.

Once processing of the action has been completed, the script then looks for an item in the <wp_prefix>_datalist table with a URL that matches the current request URL. If found, that item's content is either written to the response directly (if the full_content item flag is set) or in a basic HTML template. The script then calls exit to prevent any further processing of the request. This means that the attacker can effectively overwrite any page on the site at will, without losing the original.

An example of this script can be viewed here

A slightly different version has also been found which additionally automatically adds pages to the <wp_prefix>_install_meta table as they are indexed by Google (checks the HTTP_USER_AGENT string to see if it matches "googlebot"). This modified script also adds a filter (the_content => content_updt_theme) and action (wp_footer => content_updt_footer) which ensure that the malicious code inserted for a page is displayed in the content and footer of that page.

Analysis of Attacker Servers

apiword.press

The generated passwords are sent to this server whenever the code injector script runs.

The HTML sent back when requesting pages on this server contains links to dlwordpress.com which indicates that the two servers might be related.

Whois information for this server is protected by WhoisGuard, Inc. in Panama, meaning that we cannot use this method to see who registered this domain.

Various services offered by the server expose a self-signed SSL certificate with the following information: -

  • Country: US
  • State: California
  • Locality: San Francisco
  • Organisation: Vesta Control Panel
  • Certificate common name: gamerati2016.example.com
  • E-mail: gamerati2016@gmail.com

Removal

Removal of the exploit is fairly simple except for the some of the arbitrary changes to posts on the site. The following checklist should be followed for removal: -

  • Remove the code injector if it still exists.
  • Check each functions.php file and remove any malicious code.
  • Check for <wp_prefix>_datalist and <wp_prefix>_install_meta tables. If they contain any records, keep a note as this might help to find posts that have been modified. Then delete these tables.
  • Scan all posts for content that includes a DIV element with an ID of "wp_cd_code" and remove this DIV for each affected post.

Code injector

The script used to inject the malicious code needs to be found. In my example, the woocommerce-direct-download plugin contained a file called woocp.php, so in my case this file needed to be deleted.

Backdoor code in functions.php

All backdoor code needs to be removed from all functions.php files in the site's themes directory. This is quite straight forward as the malicious code is contained within the first <?php ?> block in the file, so removing this block is sufficient to remove the backdoor.

Conclusion

This exploit seems to have come from a malicious WordPress plug-in called woocommerce-direct-download which might have been downloaded from dlwordpress.com. While I cannot be completely sure where this plug-in came from originally, the server at apiword.press (which is used by the code injector to report generated passwords for the backdoor code) makes direct mention of other plug-ins and themes that can be downloaded from this site, so that makes it a strong possibility.

Once infected, the attacker can use the backdoor to add or modify arbitrary posts on the site. Therefore, it's difficult to see the impact of the attack directly as each site may be attacked in a different way. The exploit does however create some additional tables (<wp_prefix>_datalist and <wp_prefix>_install_meta), so this gives a good positive identification and impact of an infection.