Recently, one of our analysts @kpetku came across a series of semi-randomised malware injections in multiple WordPress environments. Typical of spam redirect infections, the malware redirects visitors by calling malicious files hosted on third party infected websites.
Interestingly, the infection stores itself as encoded content in the database and is called through random functions littered throughout plugin files using a very common wordpress function “get_option”. In this post we will review this infection and its characteristics.
Symptoms of Infection
Our client first noticed that when accessing their website through Google search results their browser was caught in a redirect chain that eventually landed them at a bogus scam site. The redirect first lands the victim at
karlliscanutma[.]gq
And subsequently redirects to
cancroid[.]buzz
Finally landing them here:
ffzbdi[.]eatoccurwriter[.]top
Shortly after reproducing this redirect, the domain was taken offline after being blacklisted by Google:
They have since moved on to new questionable domains to host their scam:
gqrsor[.]maintravelcrease[.]top
These are all typical spammy/scam sites that us security analysts are quite accustomed to seeing. This by itself is not out of the ordinary for WordPress infections, but what was atypical about this case was how it was injected into the website.
Unauthorised Third Party Content Loading in Source
The first questionable thing that we noticed that seemed to be triggering the redirect were some files named…
./wp-content/count.php
…loading through JavaScript tags from other infected websites:
Let’s investigate how this redirect code is being loaded in these pages!
Randomly Named Variables and Arguments Use get_option()
There doesn’t seem to be much rhyme or reason to the files the malware infects, nor the variable names that load the malware. What’s common between them is that they use the get_option() function which is used to render a specific record from the database into memory.
Here’s one example of the injection in the following file:
./wp-content/plugins/wp-callisto-migrator/wp-callisto-migrator.php
Here’s another in the following file:
./wp-content/plugins/genesis-connect-for-woothemes-sensei/genesis-connect-for-woothemes-sensei.php
And a third here:
./wp-content/plugins/erp-pdf-invoice/erp-pdf-invoice.php
Notice how the variable names, function names, and database option names are all different and apparently randomised, making detection more difficult.
Here is another variation where the injection is scattered in multiple areas of the same file:
./wp-content/plugins/woo-custom-empty-price/woo-custom-empty-price.php
And this example where the malware is broken up into three parts throughout the file:
./wp-content/plugins/quicklink/quicklink.php
At first glance this content does not appear to be malicious and could be easily overlooked. The get_option() function is quite commonly used by plugins to fetch settings from the database.
Moreover, when comparing the affected files to fresh copies from the WordPress repository, the files themselves bear little similarity. They appear to have had a bunch of rubbish (but seemingly benign) code added into the files in order to further bury the lead.
In the database dumps, multiple pieces of PHP function names and tags are stored unencoded in values such as:
(10239, 'section_args', 'file_put_contents', 'yes'), (17802, 'auto_update_plugins', 'a:0:{}', 'no'), (10238, 'pagelayer_pages', 'base64_decode', 'yes'), (10239, 'section_args', 'file_put_contents', 'yes'), (10240, 'columnGroups', 'tempnam', 'yes'), (10241, 'searchlist', 'sys_get_temp_dir', 'yes'), (10242, 'HolidayCount', 'rand', 'yes'), (10243, '_manager', 'unlink', 'yes'), (10244, 'source_site', '<?php\n', 'yes'), (10245, 'check_taxonomy', 'wp-callisto-migrator', 'yes'),
When the database values are called and strung together this is where we get our final payload stage and redirect.
The plugins affected are not necessarily vulnerable themselves, but as long as the plugin files are installed and activated on the website the infection will load.
Payload in the Database
This begs the question: What are all of these random functions and variables actually loading?
First we need to understand the get_option() function that is being used here. What this does is retrieve an option value from the wp_options table from the database by specifying an option name. So, if there is a database column with the option name “option1″ with the content “Hello world!”, the following would return that text:
$HelloWorld = get_option('option1');
Once we load up our database administration tool of choice and search for the three different names called by the get_option function in each file, we are met with three results:
- <?php
- base64_decode
- As well as a massive chunk of base64 encoded content:
And much more…
An additional option value stored in the database that is called by this payload is in another chunk of base64 code. The base64 code contains the list of compromised websites from which the malware grabs the injection:
Decoded and redacted:
In taking a closer look at the larger payload we see that it is crafting the JavaScript injections out of this encoded array in the database. These are called by the random function names injected into the files listed above which vary from website to website.
You can see the…
count.php
…file mentioned above referenced here. This is where the redirect javascript gets displayed in the source code of the victim website.
Another feature of this malware is that it goes to lengths to hide itself from common search engines bots. Here is the part of the injection which specifies which user agents to block from seeing the injected scripts:
Login Bypass & eval() Code Execution
In another part of this payload we see a couple additional interesting features:
- The ability to bypass login authentication to access the admin panel
- Built in eval() backdoor giving them remote code execution access
Once exploited, the attackers add a malicious user named a15de4bb0e3a4bbab1a to the website to ensure continued access:
An Atypical Infection
All in all, this is a pretty interesting infection for multiple reasons:
- The payload is extracted from a database injection in wp_options
- The admin user is hidden from view
- A backdoor is injected and encoded in the database
- The payload has multiple stages where the functions are stored separately in the database using an internal WP call get_option()
- Variables and function names are randomised across random plugin files
Additionally, this malware seems to have been designed in a very modular fashion allowing for easy customisation at a later date if desired. The encoding method could easily be changed from base64 to something else such as xor or gzinflate while keeping the rest of the chain intact.
The way that website owners can prevent their website from falling victim to this attack is detailed in various places throughout this blog:
If you ever find yourself with a compromised website you can count on us to help get it fixed!