Andi90: Bilder in zugriffsverweigerndem Ordner auslesen

--- EINLEITUNG ---

Hallo, ich bastle seit gestern an einer Möglichkeit, Bilder in einem durch .htaccess "verbotenen" Ordner via php auszulesen. Da ich mittlerweile ein wenig sehr weit weg von meiner ursprünglichen Fragestellung bin, dachte ich mir, erneut einen Beitrag dazu zu eröffnen. Sollte dies nicht erwünscht sein, so bitte ich die Administratoren, diesen Beitrag mit meinem ursprünglichen Beitrag zusammenzuführen, in diesem Fall möchte ich mich für die Neueröffnung entschuldigen. Danke an dieser Stelle speziell an dedlfix und wisch; - ihr habt mich auf die richtige Fährte gebracht. Der ursprüngliche Beitrag ist für das Verständnis dieses Beitrags nicht von Nöten, wer dennoch Interesse daran hat, der kann ihn hier einsehen: https://forum.selfhtml.org/self/2018/jan/8/problem-mit-htaccess/1711647#m1711647

--- PROBLEMSTELLUNG ---

Gegeben sind eine PHP-Datei, die auf JPEGs, die in einem zugriffverweigernden Ordner (htaccess: Require all denied) liegen, zugreifen soll. (Die PHP-Datei selbst liegt außerhalb des Ordners.)

Das funktioniert natürlich mit

<?php
$img = imagecreatefromjpeg("PFAD-ZUR-JPEG-DATEI");
imagejpeg($img, "neuer_name.php");
imagedestroy($img);
?>
<img src="neuer_name.php">

...logischerweise wird dadurch aber eine NEUE php-Datei (neuer_name.php) auf dem Server erzeugt, was natürlich nicht Sinn und Zweck der Sache ist.

--- FRAGE ZUR PROBLEMSTELLUNG ---

Wie lese ich also via PHP die JPEGs, die sich im zugriffsverweigernden Ordner befinden aus, ohne dabei neue Dateien zu erstellen?

Danke, Andi.

P.S.:

Ich hab natürlich auch schon file_get_contents() durchs Dorf getrieben, das hat allerdings nicht funktioniert (anstelle der JPEG-Datei verlinkt IMG mit file_get_contents() nur auf sich selbst, sprich die PHP-Datei). Soll heißen: für einen Hinweis auf file_get_contents() wäre ich für einen zusätzlichen Verweis auf ein BEREITS FUNKTIONIERENDES Skript überaus dankbar :D

  1. hi,

    Wie lese ich also via PHP die JPEGs, die sich im zugriffsverweigernden Ordner befinden aus, ohne dabei neue Dateien zu erstellen?

    Auslesen geht auch ohne daß eine neue Datei erstellt wird. Die Frage ist, was DU mit dem Dateiinhalt machen willst. MfG

  2. Tach!

    --- PROBLEMSTELLUNG ---

    Gegeben sind eine PHP-Datei, die auf JPEGs, die in einem zugriffverweigernden Ordner (htaccess: Require all denied) liegen, zugreifen soll. (Die PHP-Datei selbst liegt außerhalb des Ordners.)

    Das funktioniert natürlich mit

    <?php
    $img = imagecreatefromjpeg("PFAD-ZUR-JPEG-DATEI");
    imagejpeg($img, "neuer_name.php");
    imagedestroy($img);
    ?>
    <img src="neuer_name.php">
    

    ...logischerweise wird dadurch aber eine NEUE php-Datei (neuer_name.php) auf dem Server erzeugt, was natürlich nicht Sinn und Zweck der Sache ist.

    Du mischst hier zwei Dinge. Der Verweis auf eine Bild-Resource steht im HTML-Code der Response des einen Requests. Der Request nach dem Bild ist dann ein zweiter Request. Du braucht und solltest das Bild nicht in dem ersten Request bearbeiten oder bereitstellen.

    Wie lese ich also via PHP die JPEGs, die sich im zugriffsverweigernden Ordner befinden aus, ohne dabei neue Dateien zu erstellen?

    Deine Verweise sollten nicht auf Bilddateien gehen, sondern auf ein neu zu schreibendes Script. Dieses muss als Parameter den Namen der Bilddatei übergeben bekommen. Alternativ kann man das auch im Apachen mittels Rewrite so gestalten, dass der Bild-Request auf das Bild-Auslieferunggsscript geleitet wird, inklusive Übergabe des angefragten Namens - oder man holt ihn sich aus einem der $_SERVER-Einträge.

    Das Auslieferungsscript prüft nun, ob der Request auch im vorgegebenen Verzeichnis bleibt oder jemand es missbraucht, um irgendwo anders zu wildern. Dazu verknüpft man den Dateinamen aus dem Request mit dem Verzeichnis, schickt das dann durch realpath() und vergleicht, dass der Anfang mit dem Pfad zum Bildverzeichnis übereinstimmt. Falls jemand nach ../../darfstenicht fragt, kommt ein anderer Wert raus.

    Gegebenenfalls kommen noch weitere Prüfungen gemäß deiner Geschäftslogik dazu. Wenn alles gut ist, sendest du einen passenden Content-Type-Header und nimmst dann readfile() zum Ausliefern der Datei. imagejpeg() und Konsorten brauchst du nur, wenn die Grafik noch bearbeitet werden muss. In dem Fall ruft man dann am Ende imagejpeg() ohne Dateinamen auf, wodurch die Grafik lediglich aus dem Speicher gesendet wird, ohne eine Datei anzulegen.

    dedlfix.

  3. Hallo Andi,

    für das Bild muss ein separater Request erstellt werden.

    Beispiel:

    <img src="./showPic.php?name=Bild1">
    

    mit dem php-Script:

    <?php
    header("Content-Type: image/jpeg");
    readfile("./verzeichnis/".$_GET['name'].".jpeg");
    ?>
    

    Vielleicht könnte man den Bildnamen vorher noch validieren 😉.

    Siehe auch das Kapitel in SELFPHP

    Viele Grüße

    Wilfried

    1. Tach!

      <?php
      header("Content-Type: image/jpeg");
      readfile("./verzeichnis/".$_GET['name'].".jpeg");
      ?>
      

      Vielleicht könnte man den Bildnamen vorher noch validieren 😉.

      Nicht nur "könnte", sondern man muss. Ansonsten kann man sich die Zugriffsbeschränkung sparen, weil ja mit diesem zu einfachen Script mittels relativen Pfaden im Bildnamen nach überall zugegriffen werden kann.

      dedlfix.

      1. Ihr habt ja Recht - deshalb auch der Smilie.

        Aber ich wollte das Script so einfach wie möglich halten und auf das Wesentliche reduzieren.

        Sonst:

        Ich halte es mit pl: keine $_GET- und $_POST-Parameter direkt für Zugriffe verwenden - wenn es sich vermeiden lässt. Also in diesem Fall eine Liste mit expliziter Zuordnung führen und pflegen.

        Ob man eine Session oder nur eine Anmeldung über Cookies verwenden will, hängt vom Einzelfall ab, und ist auch "Geschmackssache". In jedem Fall wäre das aber ein komplett neues Fass.

        Eine Anleitung zum Session-Handling gibt es hier.

        Viele Grüße

        Wilfried

        1. hi Wilfried,

          Ich halte es mit pl: keine $_GET- und $_POST-Parameter direkt für Zugriffe verwenden - wenn es sich vermeiden lässt. Also in diesem Fall eine Liste mit expliziter Zuordnung führen und pflegen.

          Das

          1. war meine erste Sicherheitslücke mit der ich es mal zu tun hatte,
          2. ist ein guter Anfang zur Verwaltung und zum Automatisieren eines Siteimagemap

          worüber sich die Suchmaschinen freuen, weils die Bildersuche erleichtert 😉

          MfG

          --
          Lerne Schießen, treffe Freunde.
    2. hi,

      Vielleicht könnte man den Bildnamen vorher noch validieren 😉.

      Warum so umständlich? Einfacher und sicherer ist es, statt des Namen eine id als Parameter zu übergeben. Dabei bleiben Pfad/Name intern und unerlaubte Zugriffe auf beliebige Dateien sind via Parameter generell nicht mehr möglich. MfG

      1. Tach!

        Vielleicht könnte man den Bildnamen vorher noch validieren 😉.

        Warum so umständlich? Einfacher und sicherer ist es, statt des Namen eine id als Parameter zu übergeben. Dabei bleiben Pfad/Name intern und unerlaubte Zugriffe auf beliebige Dateien sind via Parameter generell nicht mehr möglich.

        Kommt auf die Aufgabenstellung an. Wenn man eine solche Übersetzungsliste von ID zu Dateinamen hat, muss man die pflegen. Jede Änderung am Bestand muss in dieser Liste nachvollzogen werden. Da wäre es einfacher, darauf zu testen, ob der Request im vorgesehenen Verzeichnis bleibt, darin aber beliebig abfragen darf.

        dedlfix.

        1. Tach!

          Vielleicht könnte man den Bildnamen vorher noch validieren 😉.

          Warum so umständlich? Einfacher und sicherer ist es, statt des Namen eine id als Parameter zu übergeben. Dabei bleiben Pfad/Name intern und unerlaubte Zugriffe auf beliebige Dateien sind via Parameter generell nicht mehr möglich.

          Kommt auf die Aufgabenstellung an. Wenn man eine solche Übersetzungsliste von ID zu Dateinamen hat, muss man die pflegen.

          Ja natürlich. Das gehört zur Projektverwaltung genauso wie die Policies. Und zwar so dokumentiert, daß man das jederzeit nachvollziehen und ggf. auch aus der Hand geben kann.

          MfG

    3. Hallo, erstmal spannend, die rege Diskussion zu beobachten! :)

      Nachdem nun zwei Stunden in der Tat NICHTS mehr ging, kam ich nach einem gehörigen Eskalationsschub massiven Selbstzweifels dahinter, dass mein JPEG ein NULL-Objekt war :/

      FACEPALM MIT DOPPEL F HUST

      ...bin jetzt wieder auf Schiene, hab Wilfrieds Idee implementiert und versuche auch, analog zu der mir auf den Weg gegebenen wirklich sehr informativen Quelle (wobei ich mich gleich in dem ein oder anderen weiterführenden Link verloren hab), die Validierung des Dateinamens umzusetzen.

      ...daher: wenn 'name' nicht leer ist, keine Slashes (/) enthält und nur wenn die Datei existiert (Frage: habe ich bezüglich Validierung da was vergessen? ...man merkt, dass ich auf diesem Terrain noch nicht ganz trittfest bin...), wird die Abfrage fortgesetzt.

      Ich bin also zurück ans Zeichenbrett und habe PHP wie folgt vergewaltigt:

      $dir = "/bsp/";
      if(!empty($_GET['name']) && !preg_match('=/=', $_GET['name'])) {
      	if(file_exists ($dir . $_GET['name']))  {
      		header("Content-Type: image/jpeg");
      		header("Content-Disposition: inline");
      		readfile("./bsp/" . $_GET['name'] . ".jpg");
      	}
      }
      

      Problem dabei: die Grafik wird mir nicht angezeigt, was an der Validierung liegt (ohne funktioniert's).

      Bitte um Hilfe, der Code sieht für mich einwandfrei aus ...?

      1. Tach!

        Problem dabei: die Grafik wird mir nicht angezeigt, was an der Validierung liegt (ohne funktioniert's).

        Fehlermeldungen einschalten, error_reporting auf E_ALL setzen und display_errors auf on. Den Content-Type-Header vorübergehend deaktivieren, weil der Browser dann die Daten direkt anzeigt und nicht als Bild zu interpretieren versucht. Die URL direkt aufrufen. Am Anfang des Binärstroms steht vielleicht eine Fehlermeldung. Alternativ kann man auch versuchen die Quelltext ansicht zu erreichen und sieht darin die Meldung. Sollte keine Meldung zu sehen sein, kann man nun Debugging betreiben und sieht aufgrund des "normalen" Content-Types die Debug-Ausgaben.

        dedlfix.

        1. Hallo,

          Es war wohl deftig Zeit für eine Pause… Nach einer kurzen Auszeit habe ich mich wieder vor den Bildschirm platziert und den Fehler ziemlich sofort entdeckt: der file_exists Abfrage fehlte die Dateiendung… (…siehe auch oben in meiner letzten [ziemlich unnötigen] Stellungnahme).

          Danke nochmals an alle, speziell an Wilfried,

          bzw auch dedlfix, der an der Sache dranblieb!

          LG, Andi