PHP-Sicherheit – Upload-Funktion

Immer wieder ist in den Nachrichten zu lesen, dass PHP unsicher sei. Das stimmt zwar, aber es stellt sich die Frage: Welche Sprache ist das nicht? Sicher bietet PHP einige Möglichkeiten, die in anderen Sprachen nicht möglich sind: so ist es z.B. per Default nicht möglich, in Java-Anwendungen eigenen Java-Code einzuschleußen. In PHP geht das von Haus aus. Aber auch in “sicheren” Sprachen können die Probleme entstehen.

Es gilt also der Grundsatz: Traue deinem Nutzer nicht!

Hält man sich strikt an diesen Grundsatz, kann eigentlich nichts schief gehen. Aber man muss sich eben daran halten.

Aus diesem Grund gehe ich heute mal genauer darauf ein, wie Upload-Daten von Usern geprüft werden können.

Die meisten Web-Anwendungen (u.a. auch WordPress) ermöglichen das Upload von Benutzer-Dateien und beschränken diese Möglichkeit auf bestimmte Dateiendungen. Das halte ich für ein Sicherheitsrisiko! Die Endung einer Datei macht bekanntlich keinerlei Aussagen darüber, was für eine Datei wir wirklich haben. So ging erst vor ein paar Monaten eine Sicherheitslücke des Internet Explorers durch die Medien, weil dieser JavaScript innerhalb von GIF-Dateien ausführte. Es ist also in diesem Fall nicht das drin, was drauf steht.

Meiner Meinung nach muss man mit solchen Lücken des Benutzer-Systems immer rechnen. Es reicht nicht zu sagen: “Ist nicht mein Problem, der Fehler stammt vom IE.” Betroffene Nutzer werden sich genau daran erinnern, wo sie sich etwas eingefangen haben. Und das sollte man sich nicht leisten wollen: es dauert lange, bis man einen guten Ruf bekommt. Ein schlechter Ruf bekommt man dafür umso schneller.

Aber auch auf Servern existiert dieses Risiko. So wäre es denkbar, dass innerhalb einer JPEG-Datei PHP-Code enthalten ist. Ist der Server fehlerhaft eingestellt oder gelingt es einem Hacker, die .htaccess-Datei anzupassen, so wird jedes JPEG-Bild vor dem Ausliefern auf dem Server ausgeführt (inkl. PHP-Code). Auf diese Weise wäre es ein einfaches, Cookies des Benutzers auszulesen (z.B. Login-Daten), ohne dass es auch nur irgendjemand merkt (die Bild-Informationen könnte man ja entsprechend ausliefern). Solche Risiken sind sehr gefährlich: welcher Admin prüft schon regelmäßig, ob jemand an der Server-Einstellung gefummelt hat?

Eine weitere Angriffsmöglichkeit bestünde darin, dass bekannte Lücken von Programmen ausgenutzt werden (evtl. Bufferoverflow von Programmen). So ist evtl. das direkte Unterschieben präparierter Daten möglich.

Aus diesem Grund reicht es meiner Meinung nach nicht, Dateien lediglich auf Ihre Endung zu prüfen. Systeme wie WordPress, in denen Nutzer Daten hochladen können, sind deshalb ein potentielles Risiko. Es ist also die Frage, wie kann ich sicherstellen, dass in den Dateien das drin ist, was drauf steht?

Für Bilder ist diese Möglichkeit sehr einfach: die Funktion getimagesize übernimmt die Prüfung der Bildformate GIF, JPEG, PNG und sogar des Flash-Formats SWF. Handelt es sich um gültige Dateien, werden Bildinformationen zurückgeliefert. Ansonsten erhält man als Rückgabewert “NULL”. Spätestens hier sollte man das hochgeladene Bild wieder Löschen, den Benutzer informieren und sicherheitshalber die Daten des Nutzers protokollieren, um ihn bei erneutem Versuch auszusperren.

Andere Formate sind schon komplizierter. Für viele Formate hilft aber die OpenSource-Bibliothek getID3, welche neben Audio, Video und Bildformaten auch gepackte Formate prüfen kann. Die Bibliothek kann natürlich noch weit mehr, aber sie prüft eben auch die Inhalte der Dateien.


include_once("getid3/getid3.php");
$file="test.mp3";
$tester = new getID3;
$filedata = $tester->analyze($file);

Nun sind in filedata alle Informationen enthalten, unter anderem das Audio-Format (unabhängig von der Endung) als auch Bitraten,…

Mit der Bibliothek können also schon einige Datei-Typen auf Inhalt geprüft werden. Für die Restlichen wird es schwieriger. Doch stellt PHP die Funktion mine-content-type zur Verfügung. Diese liefert zu einer Datei den angegebenen Mime-Type. Damit ist es möglich, den angegebenen Inhalt einer Datei unabhängig vom Dateinamen zu erhalten. Die Funktion stellt aber nicht sicher, dass es dann auch wirklich der korrekte Inhalt ist (der Mime-Type könnte ja gefälscht sein). Hier also aufpassen! Eine Möglichkeit, hier zu testen, besteht darin, den Dateiinhalt auf verdächtige Elemente zu durchsuchen. Das hängt jedoch vom Einsatzgebiet ab.

8 Gedanken zu „PHP-Sicherheit – Upload-Funktion

  1. Nun, im Grunde ist Dein Beitrag richtig, aber getimagesize reicht bei weitem nicht aus. Der Sachverhalt ist dieser: findet der IE in den ersten 255 Bytes der Datei HTML tags, so wird diese als HTML behandelt – unabhängig davon was der Server sagt. Ich kann Dir gerne gültige .bmp Dateien schicken, die der IE als HTML behandelt – etc.
    Wobei früher noch GIFs, PNG,s und JPEGs auch für so etwas verwendet werden konnten, ist der IE7 nur noch bei manchen Bildformaten so leichtgläubig. IE8 ist besser, da er sich durch HTTP Header beeinflussen lässt, aer toll ist das auch nicht.

    Für Anwendungsprogrammierer heisst das: Dateien nach HTML Tags scannen und gegebenenfalls zurückweisen – machen nur sehr wenige Scripte richtig.

    ~H

  2. @hsudhof: Das finde ich jetzt eine äußerst interessante Bemerkung. Soweit ich die Problematik in Verbindung mit phpBB damals mitbekommen habe, prüft getimagesize nämlich das Bild wirklich darauf, ob das Bild korrekt ist. Und wenn ja, dann bekomme ich erst die Bildinformationen (wie Breite, Höhe, Typ, etc.). Hast du mir eine Quelle, wo ich deinen Einwand nochmals genauer folgen kann?

  3. Nun, phpBB – in der aktuellen Inkarnation – hatte damit keine grösseren Probleme.
    Getimagesize prüft nur den Header der Bildateien, aber selbst bei einer kompletten Verifikation löst dies das Problem nicht: selbst 100% valide Bilddateien (und andere Dateien) können Schadvektoren beinhalten.

    Hier ein Beispiel:
    http://asktheasshats.com/pub/hi.bmp (mit dem IE <= 7.0)

    Ich bereite selbst gerade einen Blogpost über unsere Erfahrungen dahingehend vor, bis dahin:

    http://blogs.msdn.com/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx

    und

    http://christ1an.blogspot.com/2007/02/ms-mime-type-detection-prevention.html

    ~H

  4. @hsudhof: Hab das jetzt nochmals bei mir getestet: getimagesize liefert tatsächlich die Informationen, dass es ein gültiges Bild ist, obwohl in der Datei JS-Code enthalten ist. Allerdings hat das Bild bei mir im IE6 keinerlei Auswirkung, JavaScript wird nicht ausgeführt.

    Trotzdem danke für den Hinweis: dass das Bild überhaupt angenommen wird, ist schon ein kritischer Punkt. Jedoch bin ich derzeit am Zweifeln, ob ein String-Vergleich wirklich den gewünschten Effekt bringt. Das hat mehrere Gründe: zum einen ist es bei Bildern möglich, Texte zu hinterlegen (was hier dann zu fehlerhaften Ablehnungen führen würde), andererseits hindert es ja keinen Hacker, den JS-Code codiert einzufügen. Das würde sehr wahrscheinlich ja auch ausgeführt werden. Und alle Kodierungsmöglichkeiten abfragen?

  5. Nun, es handelt sich um ein halbdokumentiertes Feature des IE. Ich habe gerade keinen IE6 zur Hand, aber IE7 zeigt mir einen schönen JS Gruß – solange das Bild über HTTP kommt, also nicht im lokalen Dateisystem liegt.

    In meinen Experimenten waren Kodierungen etc ungefährlich; dieses Verhalten tritt nur auf, wenn IE glaubt HTML vor sich zu haben – was er nur bei gewissen Bitmustern tut.

    http://www.phpbb.com/blog/2008/10/25/attachment-headaches-with-the-internet-explorer/

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.