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: -
- 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 inwp-config.php
. The code used to generate this hash ismd5($_SERVER['HTTP_HOST'] . AUTH_SALT)
. - Replaces the
{$PASSWORD}
string in the Base64-encoded payload ($install_code
). - Creates two new database tables:
<wp_prefix>_install_meta
and<wp_prefix>_datalist
. - Searches the themes directory of the site for
functions.php
files. - For each
functions.php
file, injects the modified payload (see step 2) into the file as long as it isn’t already there. - 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 ofo.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 calledlicense.html
in the site’s root. - 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 ofwp_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 ofwp_cd_code
in a post specified by ID. If the DIV exists, the contents are updated with thedata
passed in the request.create_page
: based on a passedremove_page
orcontent
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 the exit
function 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
- IP: 185.127.25.246
- Country: Russian Federation
- Lat./lng.: 55.738602, 37.606800
- AS: AS206977 (Contell LLC)
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.