sigger: Sicherheit von Dateiuploads

Hallo Leser und Schreiber,

fast jeder Entwickler wird im Laufe seiner Web-Karriere zumindest einmal mit dem Thema Datei-Upload in Berührung gekommen sein. Dank recht unkomplizierter Handhabe der benötigten Technologien ist die bloße Implementierung schnell durchgeführt und das Ziel, das Hochladen von Dateien, ist erreicht. Schön- oder?

Wie immer beim Übertragen oder Speichern von Daten von einem Benutzer ist natürlich größte Vorsicht geboten. Für Entwickler die schon alleine bei Textfeldeingaben aufgrund möglicher  SQL-Injections Angstschweiß auf der Stirn bekommen, ist ein Dateiupload sicher ein Grund in suizidalen Gedanken zu versinken.

Der Inhalt einer Datei lässt sich in der Regel weniger einfach durchsuchen als ein kleiner String. Ein "Escapen" von Dateiinhalten ist im Bezug der "Kosten / Nutzen" Rechnung ebenso fragwürdig.

Dennoch haben sich ein paar bekannte Möglichkeiten etabliert dennoch ein Mindestmaß an Sicherheit gewährleisten zu können.

1. Prüfen der Dateiendung der hochgelandenen Datei.
2. Prüfen des Mime-Types
3. Prüfen der Dateigröße und Einschränkung der Maximalgröße

Was aber eigentlich viel Interessanter ist, ist die Frage, was eigentlich mit der Datei die vom Webclient gesendet wird auf dem Server passiert.

Laut der PHP-Doku wird die Datei in dem Standard TMP Ordner des Servers hinterlegt, dass in der Regel nicht durch das Web erreichbar ist. Diese Grundidee ist einerseits beruhigend, da so die Benutzerdatei nicht durch einen Aufruf aus dem www. Ausgeführt werden kann, andererseits befindet sich nun ein Fremdkörper im regulären Dateisystem des Servers, wenn auch nur für die Dauer des Requests.

"Wurde die Datei in dem temporären Verzeichnis nicht verschoben oder umbenannt, wird sie am Ende des Requests gelöscht." Zitat: http://de2.php.net/manual/de/features.file-upload.php.

Ein weiterer, grundlegender Aspekt der Sicherheit eines Uploaders, ist das Fortfahren der Arbeit mit der Datei. Entweder sie wird an ihren eigentlichen Einsatzort kopiert, etwa für eine Art Web-FTP oder sie wird sonst wie weiter modifiziert.
Diese Art des Datenhandlings ist meiner Ansicht nach das gefährlichste, da eine Datei mit beliebigen Inhalten von einem Anwender ausgeführt werden könnte. Hier hilft dann besten Falls der save_mode.

Weitergehend interessant ist der Bezug von einem Datei-Upload zu Datenbank-Blobs.
Hier drängt sich wieder die Frage der Gefahr durch Injections auf, wenn man den "Stream" einer Datei in der Datenbank speichert.

Bereits getestet habe ich das Hochladen von XML-Dateien. Eine XML-Datei mit folgendem Inhalt:

<?xml version="1.0"?>
<foo>
<image> ' SELECT * FROM somewhere-- </image>
</foo>

Passiert Problemlos sogar einen Test mit Dom::loadXML, da es ja valide ist.

Da man klassischer Weise in einer XML-Datei nicht alle Anführungszeichen escapen sollte, steht man hier vor einem Problem, da das oben stehende SQL-Statement in einerm INSERT ausgeführt werden würde(!).

Die einzige Lösung die bisher bekannt ist, ist das speichern solcher Datensätze, die vorher Base64 kodiert werden.

Mir wäre es sehr lieb, wenn Ihr Eure Erfahrungen in Bezug auf Dateiuploads hier schildern würdet. Besonderes Interesse besteht natürlich an Negativerfahrungen, auch wenn diese keinem Gewünscht werden.

Wie handelt Ihr Datenstreams die als Blob gespeichert werden sollen?

Bin für jede Diskussion, Meinung und Erfahrung dankbar.

Danke fürs Lesen.

Mit freundlichen Grüßen

  1. Hallo,

    Hier drängt sich wieder die Frage der Gefahr durch Injections auf, wenn man den "Stream" einer Datei in der Datenbank speichert.

    wieso? Behandle die Daten kontextgemäß und Du hast kein Problem.

    Bereits getestet habe ich das Hochladen von XML-Dateien. Eine XML-Datei mit folgendem Inhalt:

    <?xml version="1.0"?>
    <foo>
    <image> ' SELECT * FROM somewhere-- </image>
    </foo>

    Passiert Problemlos sogar einen Test mit Dom::loadXML, da es ja valide ist.

    ja und?

    Da man klassischer Weise in einer XML-Datei nicht alle Anführungszeichen escapen sollte, steht man hier vor einem Problem, da das oben stehende SQL-Statement in einerm INSERT ausgeführt werden würde(!).

    Unsinn. Es handelt sich hier um stinknormale Daten, die kontextgemäß aufbereitet werden. Ob das die entsprechenden Maskierfunktionen passend zum DBMS sind, oder ob man prepared Statements verwendet, das ist gleichgültig. Ordentlich behandelt, kann nichts passieren. Dem DBMS ist es völlig egal. XML-Daten sind keine andere Daten als sonstige Benutzereingaben. Sie müssen ganz genauso behandelt werden.

    Wie handelt Ihr Datenstreams die als Blob gespeichert werden sollen?

    wie sonstige Daten auch. Sie werden kontextgemäß behandelt. Es besteht *kein* Unterschied.

    Freundliche Grüße

    Vinzenz

    1. Hallo Vinzenz,

      danke für deine Antwort.

      Unsinn. Es handelt sich hier um stinknormale Daten, die kontextgemäß aufbereitet werden. Ob das die entsprechenden Maskierfunktionen passend zum DBMS sind, oder ob man prepared Statements verwendet, das ist gleichgültig. Ordentlich behandelt, kann nichts passieren. Dem DBMS ist es völlig egal. XML-Daten sind keine andere Daten als sonstige Benutzereingaben. Sie müssen ganz genauso behandelt werden.

      "Normal" würde ich einfach mittels mysql_real_escape_string() die Eingabe des Benutzers entschärfen und ggf. vorher mit is_numeric() etc. prüfen.

      Mit XML kommt es leider nicht in Frage, da aus:

      <smp>
      <foo var="bar">Blub</foo>
      </smp>

      Das werden würde:

      <smp>
      <foo var="bar">Blub</foo>
      </smp>

      Ordentlich behandelt, kann nichts passieren

      Schön zu wissen. Aber wie würdest du denn mit diesem Beispiel oben umgehen, wenn du es in der Datenbank abzulegen hättest?

      Grüße

      1. Hallo,

        Unsinn. Es handelt sich hier um stinknormale Daten, die kontextgemäß aufbereitet werden. Ob das die entsprechenden Maskierfunktionen passend zum DBMS sind, oder ob man prepared Statements verwendet, das ist gleichgültig. Ordentlich behandelt, kann nichts passieren. Dem DBMS ist es völlig egal. XML-Daten sind keine andere Daten als sonstige Benutzereingaben. Sie müssen ganz genauso behandelt werden.

        "Normal" würde ich einfach mittels mysql_real_escape_string() die Eingabe des Benutzers entschärfen und ggf. vorher mit is_numeric() etc. prüfen.

        wenn Du diese veralteten Funktionen noch nutzt, dann ist das korrekt.

        Mit XML kommt es leider nicht in Frage, da aus:

        doch selbstverständlich.

        <smp>
        <foo var="bar">Blub</foo>
        </smp>

        <smp>
        <foo var="bar">Blub</foo>
        </smp>

        nicht im DB-Feld :-) Da steht der Originaltext. Die Behandlung ist ja nur für die Textschnittstelle.

        Ordentlich behandelt, kann nichts passieren

        Schön zu wissen. Aber wie würdest du denn mit diesem Beispiel oben umgehen, wenn du es in der Datenbank abzulegen hättest?

        wenn ich die mysql_*-Funktionen benutzen müsste, dann nähme ich mysql_real_escape_string.

        Freundliche Grüße

        Vinzenz

        1. Hello,

          wenn Du diese veralteten Funktionen noch nutzt, dann ist das korrekt.

          Deine Darstellung irritiert mich jetzt.
          Dind denn die "veralteten Funktionen" unsicher?
          Gibt es da Lücken, die einfache Anwendungen von MySQL gefährden könnten?

          Liebe Grüße aus Syburg bei Dortmund

          Tom vom Berg

          --
          Nur selber lernen macht schlau
          http://bergpost.annerschbarrich.de
          1. Hallo Tom,

            wenn Du diese veralteten Funktionen noch nutzt, dann ist das korrekt.
            Deine Darstellung irritiert mich jetzt.
            Dind denn die "veralteten Funktionen" unsicher?

            sie sind veraltet. Sie sind nicht in der Lage, erweiterte Features von MySQL 4.1 und neuer zu nutzen. Sie sind nicht mehr zeitgemäß.

            Gibt es da Lücken, die einfache Anwendungen von MySQL gefährden könnten?

            So etwas habe ich keine Silbe behauptet. Unter mysqli könnte man zum Beispiel prepared Statements nutzen.

            Freundliche Grüße

            Vinzenz

            1. Hello,

              Gibt es da Lücken, die einfache Anwendungen von MySQL gefährden könnten?

              So etwas habe ich keine Silbe behauptet. Unter mysqli könnte man zum Beispiel prepared Statements nutzen.

              *ups*
              Warum fühlst Du dich angegriffen? Ich wollte doch nur wissen, ob Du etwas über Lücken in den älteren Funktionen weißt. Da hätte mir jetzt eine sachliche Antwort vollkommen genügt.

              Prepared Statements sind außerdem ein vollkommen anderes Konzept: weg von einer standarisierten Abfrage-Sprache, back to the Roots.

              "Prepared Statements" konnte, etwas großzüging betrachtet, bereits der bTrieve-Manager, der zur gleichnamigen Datenbank gehörte. Diese wurde von els2 (Novelll-Netware-Vorgänger) verwendet.
              Der Btrieve-Requester hatte mWn 35 Blockbuffer-Funktionen, die vollkommen ausreichend waren, um mit gemeinsamen Daten im Netzwerk umzugehen und war schon zu 80286-Zeiten verflixt schnell. Das war so ab 1980 und wurde dann in Novells Netware integriert, ohne dass es damals großartig publiziert wurde. So hatte man auch damals schon eine äußerst leistungsfähige Datenbank für konkurrierende Zugriffe zur Verfügung ohne einen Pfennig dazubezahlen zu müssen *gg*.

              Später wurde das durch "SQL-Scalable" ergänzt.

              Ich denke, dass MySQL nun ca. 25 Jahre später einfach das Rad neu erfindet.

              Liebe Grüße aus Syburg bei Dortmund

              Tom vom Berg

              --
              Nur selber lernen macht schlau
              http://bergpost.annerschbarrich.de
              1. echo $begrüßung;

                Prepared Statements sind außerdem ein vollkommen anderes Konzept: weg von einer standarisierten Abfrage-Sprache, back to the Roots.

                Das stimmt so nicht, denn es wird immer noch ein SQL-Statement benötigt. Nur werden jetzt Platzhalter statt der Werte dort eingefügt und die eigentlichen Werte anderweitig transportiert. Von der Abfragesprache wird dabei nicht abgerückt.

                "Prepared Statements" konnte, etwas großzüging betrachtet, bereits der bTrieve-Manager, der zur gleichnamigen Datenbank gehörte.

                Den kenne ich nicht, aber ich kenne dBase aus der Zeit, als es noch kein SQL konnte. Da ging das Datenmanipulieren direkter, ohne ein deutlich aufwendigeres SQL-Statement erstellen zu müssen. Allerdings erinnere ich mich nicht mehr, wie genau das damals aussah.

                Ich denke, dass MySQL nun ca. 25 Jahre später einfach das Rad neu erfindet.

                Das ist nicht so, denn Prepared Statements waren in anderen DBMS schon eher implementiert als in MySQL. Und wie schon erwähnt sind Prepared Statements nur eine Erweiterung von SQL-Statements und kein großartig anderes Konzept.

                echo "$verabschiedung $name";

  2. Hello,

    Dennoch haben sich ein paar bekannte Möglichkeiten etabliert dennoch ein Mindestmaß an Sicherheit gewährleisten zu können.

    1. Prüfen der Dateiendung der hochgelandenen Datei.
    2. Prüfen des Mime-Types
    3. Prüfen der Dateigröße und Einschränkung der Maximalgröße

    4. Das Ziel des Uploads, das unter gewissen Umständen vom Client manipulierbar ist
       Dies trifft in min. 20% der von mir untersuchten Fälle zu

    Liebe Grüße aus Syburg bei Dortmund

    Tom vom Berg

    --
    Nur selber lernen macht schlau
    http://bergpost.annerschbarrich.de
      1. Das Ziel des Uploads, das unter gewissen Umständen vom Client

      Hallo Tom,

      danke für deine Antwort.
      Die einzige Möglichkeit für einen solche Upload müsste eine "krasse" Sicherheitslücke sein, wenn der Uploader etwa so aussieht:

      --- DOOFES BEISPIEL! --
      <form enctype="multipart/form-data" action="smp.html?dir=userupload" method="post">

      Und der Entwickler dann $_GET['userdir']; voll vertraut. Oder hast du noch andere Erfarungen gemacht?

      Danke!

      1. Hello,

        danke für deine Antwort.
        Die einzige Möglichkeit für einen solche Upload müsste eine "krasse" Sicherheitslücke sein, wenn der Uploader etwa so aussieht:

        --- DOOFES BEISPIEL! --
        <form enctype="multipart/form-data" action="smp.html?dir=userupload" method="post">

        Und der Entwickler dann $_GET['userdir']; voll vertraut. Oder hast du noch andere Erfarungen gemacht?

        Ja.
        Es wird (4.) serienmäßig unter dem übermittelten Namen der lokalen Datei abgespeichert und das

        (5.) in einem per HTTP zugänglichen Pfad

        Dann ist es kaum noch schwierig, die Ressource auch aktiv zum machen. Das hängt jedoch von der verwendeten Distribution nebst OS ab, denn jede hat andere Eigenheiten. Die meisten konnte ich bei Suse Linux 10.0 feststellen.

        Ein Grundfehler ist es, nur bestimmte MIME-Types zu verbieten, statt dass man nur ganz wenige und genau erkannte MIME-Types erlaubt für das Upload.

        Probleme gibt es da mit neuen Types, die in der MIME.Magic-Datei noch nicht aufgeführt sind oder für die es noch keine Sicherheit gibt, dass es keine Lücken mehr gibt...

        Zwei unabhängige Prüfungen sind daher immer besser, als eine einzelne und dudem eventuell unsichere.

        Liebe Grüße aus Syburg bei Dortmund

        Tom vom Berg

        --
        Nur selber lernen macht schlau
        http://bergpost.annerschbarrich.de
  3. echo $begrüßung;

    Wie immer beim Übertragen oder Speichern von Daten von einem Benutzer ist natürlich größte Vorsicht geboten.

    Diese "Vorsicht" ist mit jeglichen Daten geboten, wann immer sie in einen anderen Kontext gebracht werden sollen. Ob beispielsweise ein " aus einer Benutzereingabe stammt oder von einer vertrauenswürdigen Quelle ist unerheblich. Es muss immer kontextgerecht notiert werden. In HTML als &quot; und für SQL-Statements maskiert gemäß den Regeln des jeweiligen Dialekts. (Ausnahmen wie «" muss in HTML nur in Attributwerten beachtet werden», lass ich mal unbeachtet.)

    Für Entwickler die schon alleine bei Textfeldeingaben aufgrund möglicher  SQL-Injections Angstschweiß auf der Stirn bekommen, ist ein Dateiupload sicher ein Grund in suizidalen Gedanken zu versinken.

    Solche Entwickler sollten sich mit dem Grund für die kontextgerechte Behandlung vertraut machen. SQL-Injections sind nur eine mögliche, schädliche Ausnutzung "vergessener" Behandlung. Auch harmlose Syntaxfehler können dabei auftreten.

    Der Inhalt einer Datei lässt sich in der Regel weniger einfach durchsuchen als ein kleiner String. Ein "Escapen" von Dateiinhalten ist im Bezug der "Kosten / Nutzen" Rechnung ebenso fragwürdig.

    Fragwürdig ist hier eigentlich nur dein Verständnis von der Materie. Nimm mal ein SQL-Statement à la

    SELECT feld FROM tabelle WHERE feld="wert"

    Hier werden Befehlsbestandteile mit Nutzdaten gemischt. «wert» ist in dem Fall ein Nutzdatum. Damit es von Befehlsbestandteilen (beispielsweise einem Feldnamen) unterschieden werden kann, ist es in Anführungszeichen zu setzen. Damit ist klar gekennzeichnet, wo das Nutzdatum anfängt und wo es aufhört und die Anweisung weitergeht. Bis auf den Fall, dass im Nutzdatum ein Anführungszeichen enthalten ist. Das hieße ein vorzeitiges Ende des Wertes. Als Abhilfe wird es (unter MySQL) durch einen vorangehenden Backslash maskiert. Somit erkennt es der Empfänger als Datenbestandteil an. Es ist dabei völlig unerheblich, ob die Daten nur wenige Zeichen lang sind oder den Inhalt einer Datei darstellen. Der Empfänger muss stets Klarheit über die Bedeutung der Zeichen haben.

    Deine Bedenken rühren wohl daher, dass nun der Wert durch Backslashes verfälscht ist. Das ist jedoch nur für den Kontext des SQL-Statements so. Wenn dieses vom DBMS ausgewertet wird, werden die Nutzdaten daraus extrahiert und von den Transportsicherungsbackslashes befreit. Die Daten liegen nun wieder im Original vor.

    Zumindest für SQL-Statements kann man sich das Procedere sparen, wenn man Prepared Statements verwendet. Hier wird im SQL-Statement nur ein Platzhalter angegeben. Die eigentlichen Nutzdaten gelangen auf anderem Weg zum DBMS. Da sie dabei nicht in einem SQL-Statement-Kontext transportiert werden, ist keine Behandlung dafür erforderlich. [1]

    Dennoch haben sich ein paar bekannte Möglichkeiten etabliert dennoch ein Mindestmaß an Sicherheit gewährleisten zu können.

    1. Prüfen der Dateiendung der hochgelandenen Datei.
    2. Prüfen des Mime-Types
    3. Prüfen der Dateigröße und Einschränkung der Maximalgröße

    Punkt 1 und 2 lassen sich vom Absender beliebig fälschen. Punkt 3 ist kaum relevant. Man kann auch ZIP-Files erstellen, die nur eine geringe Größe haben, beim Auspacken aber beliebig groß werden können. Siehe: Archivbombe

    Solange du keine eigene Inhaltsanalyse machst, kannst du nicht vom schadlosen Inhalt einer Datei ausgehen. Und selbst dann kann es Fehler in anderen Systemen geben, die beim Interpretieren der Daten Probleme bereiten können.

    Der einzige Schutz besteht im Prinzip nur darin, zu überprüfen, dass die Daten der jeweiligen Spezifikation entsprechen und zu hoffen, dass die weiterverarbeitenden Programmteile robust genug programmiert sind.

    Ein weiterer, grundlegender Aspekt der Sicherheit eines Uploaders, ist das Fortfahren der Arbeit mit der Datei. Entweder sie wird an ihren eigentlichen Einsatzort kopiert, etwa für eine Art Web-FTP oder sie wird sonst wie weiter modifiziert. Diese Art des Datenhandlings ist meiner Ansicht nach das gefährlichste, da eine Datei mit beliebigen Inhalten von einem Anwender ausgeführt werden könnte. Hier hilft dann besten Falls der save_mode.

    Inwieweit soll dich der SafeMode (safe wie sicher, nicht save wie speichern) hier schützen? Zumal er auch ab PHP6 nicht mehr vorhanden sein wird, unter anderem weil seine Nebenwirkungen mehr störten als damit Sicherheit erreicht wurde.

    Weitergehend interessant ist der Bezug von einem Datei-Upload zu Datenbank-Blobs.
    Hier drängt sich wieder die Frage der Gefahr durch Injections auf, wenn man den "Stream" einer Datei in der Datenbank speichert.

    Siehe oben. Ein Blob ist im Prinzip nichts anderes als eine Benutzereingabe und ganz genau so zu behandeln.

    Da man klassischer Weise in einer XML-Datei nicht alle Anführungszeichen escapen sollte, steht man hier vor einem Problem, da das oben stehende SQL-Statement in einerm INSERT ausgeführt werden würde(!).

    Ebenfalls siehe oben. In einem SQL-Statement ist ein Stück XML kein solches mehr sondern nur noch eine Abfolge von Zeichen. Davon sind einige aufgrund des Kontextes zu maskieren. Dass diese Zeichenfolge später mal wieder als XML interpretiert werden wird ist unerheblich. Bis dahin kann sie noch beliebig viele Kontexte durchqueren und muss dabei stets gemäß des jeweiligen Kontextes notiert werden. Ein Empfänger muss die Nutzdaten wieder aus dem Kontext-Korsett befreien und arbeitet intern immer mit dem Original, bis er sie in den nächsten Kontext überführt.

    [1] Wenn du dich an der Stelle nicht gefragt hast: "Für welchen Kontext müssen sie denn dann aufbereitet werden?" dann musst du deine Kontextwechsel-Erkennungsfähigkeiten noch etwas trainieren.
    Sie müssen für Prepared Statements gar nicht behandelt werden, da der P.S.-Mechanismus die Daten im Original erhalten möchte und selbst für einen ordnungsgemäßen Transport sorgt.

    echo "$verabschiedung $name";