Frank: Downloadskript mit geschütztem Ordner

Ich möchte Downloads aus einem geschützten Ordner ausgeben, so dass nur (per Session) registrierte Nutzer Zugriff darauf haben.

Download der Datei:

  
$filename="docs/test.doc";  
  header("Content-Disposition: filename=\"$filename\"");  
  header("Content-Type: ".mime_content_type($filename));  
  readfile("$filename");  

Wie kann ich aber der Ordner "docs" vor unerlaubtem Zugriff (z.B. über die Eingabe der URL im Browser) schützen, während das obige Script weiterhin funktioniert?

Wenn ich die Rechte des Ordners per FTP ändere, ergibt readfile() eine Fehlermeldung der Art:
"Warning:  readfile(docs/test.doc): failed to open stream: Operation not permitted in /www/htdocs/xxxx/download.php on line 17"

  1. Hello,

    Wie kann ich aber der Ordner "docs" vor unerlaubtem Zugriff (z.B. über die Eingabe der URL im Browser) schützen, während das obige Script weiterhin funktioniert?

    Wenn der Server ein Apache-Server ist, sollte es am einfachsten gehen, dass Du das Verzeichhis einfach auf .ht_download umbenennst, wobei das führende '.ht' das wesentliche ist.

    Das Script selber darf dann natürlich nicht in idesem Verzeichnis liegen.

    Gib bitte mal Bescheid, ob es geklappt hat.

    Ein harzliches Glückauf

    Tom vom Berg

    --
    Nur selber lernen macht schlau
    http://bergpost.annerschbarrich.de
    1. Vielen Dank für den Tip, scheint zu funktionieren.
      Aber ist das eine "saubere" Lösung, oder könnte mir z.B. eine künftiges Apache Update Probleme machen?

      1. Hello,

        Vielen Dank für den Tip, scheint zu funktionieren.
        Aber ist das eine "saubere" Lösung, oder könnte mir z.B. eine künftiges Apache Update Probleme machen?

        Das kann ich nicht vorhersehen.
        Aber, da es schon solange ich den Apachen kenne, ein Standard-Feature der Konfiguration ist, vermut sich, dass das so erhalten bleibt.

        Ich habe Dir mal den Abschnitt aus einer apache2.conf herausgesucht, der das regelt:

        AccessFileName .htaccess

        #
            # The following lines prevent .htaccess and .htpasswd files from being
            # viewed by Web clients.
            #
            <Files ~ "^.ht">
                Order allow,deny
                Deny from all
            </Files>

        TypesConfig /etc/mime.types

        Wie Du hier sehen kannst, kannst Du auch jeden beliebigen anderen Namen nehmen.
        Der Namensbeginn '.ht' ist aber eben Standard.

        Unter Files und FilesMatch findest Du die Erläuterung

        http://httpd.apache.org/docs/2.2/mod/core.html#files

        Ein harzliches Glückauf

        Tom vom Berg

        --
        Nur selber lernen macht schlau
        http://bergpost.annerschbarrich.de
        1. Eine Grafik aus diesem Ordner kann ich aber nicht mehr verwenden, oder?
          Also in der Art:

            
          <html>  
          <body>  
            <h1>Geschütztes Bild</h1>  
            <img src='.ht_docs/test.jpg'>  
          </body>  
          </html>  
          
          
          1. Lieber Frank,

            Eine Grafik aus diesem Ordner kann ich aber nicht mehr verwenden, oder?
            [...]
              <img src='.ht_docs/test.jpg'>

            stimmt, das ist nicht mehr möglich.

            Liebe Grüße aus Ellwangen,

            Felix Riesterer.

            --
            ie:% br:> fl:| va:) ls:[ fo:) rl:° n4:? de:> ss:| ch:? js:) mo:} zu:)
    2. Lieber Tom,

      Wenn der Server ein Apache-Server ist, sollte es am einfachsten gehen, dass Du das Verzeichhis einfach auf .ht_download umbenennst, wobei das führende '.ht' das wesentliche ist.

      man könnte den Zugriff auch dadurch verhindern, indem man per .htaccess ein Passwort verlangt, ohne das ein HTTP-Request vom Server abgelehnt wird. Wenn man dann kein passendes Passwort einrichtet, dann kann man die Datei nicht direkt downloaden, sondern ist auf ein auslieferndes PHP-Script angewiesen, das die Daten über einen anderen HTTP-Request anbietet. Mit PHP kann man ja die Datei unter einer völlig anderen URL ausliefern, als diese physikalisch tatsächlich gespeichert ist. Und dann ist auch ein Anmelde-Mechanismus mit Sessions und allem pipapo möglich...

      Liebe Grüße aus Ellwangen,

      Felix Riesterer.

      --
      ie:% br:> fl:| va:) ls:[ fo:) rl:° n4:? de:> ss:| ch:? js:) mo:} zu:)
      1. ... sondern ist auf ein auslieferndes PHP-Script angewiesen, das die Daten über einen anderen HTTP-Request anbietet. Mit PHP kann man ja die Datei unter einer völlig anderen URL ausliefern, als diese physikalisch tatsächlich gespeichert ist.

        »»

        Wie macht man das?

        1. Lieber Frank,

          nehmen wir einmal an, Du hättest alle Dateien, die man downloaden können soll, in einem Ordner namens "geheim" versteckt, der auch wegen eines (nicht definierten) Passwortes über den Browser unerreichbar ist. PHP ist jedoch auf Dateisystem-Ebene durchaus in der Lage, diese Datei zu lesen und zu schreiben. Daher könnte ein Script auf diese Daten auch von anderer Stelle aus zugreifen.

          Nehmen wir weiter an, dass Deine Verzeichnis-Struktur so aussieht:
          DocumentRoot
           |
           +--- download.php
           |
           |
           +--- geheim
           |      +-- unterordner
           |      |        |
           |      |        +-- bild_a.gif
           |      |        |
           |      |        +-- bild_b.gif
           |      |
           |      +-- .htaccess
           |      |
           |      +-- datei1.dat
           |      |
           |      +-- datei2.dat
           |
           +-- anderer_ordner
           |      |
           |      +-- datei_xyz.php

          Nun testen wir einen Browseraufruf direkt: http://example.org/geheim/datei1.dat
          Dieser Request wird mit einer Nachfrage nach Benutzernamen und Passwort quittiert, da wir in der .htaccess definiert haben, dass man eine gültige Authentifizierung braucht, um dort erfolgreich eine Datei zu erhalten - aber da es keine gültige Authentifizierung gibt, endet der Zugriff im Nichts. Schön! Dasselbe geschieht übrigens mit allen Dateien in eventuellen Unterordnern von "geheim" auch.

          Nun rufen wir ein PHP-Script auf. In diesem Beispiel ist es im DocumentRoot, könnte aber im Prinzip auch in einem beliebigen anderen Unterverzeichnis liegen, nur nicht in geheim...

          http://example.org/download.php?f=datei1.dat
          Das PHP-Script kann nun prüfen, ob der Benutzer sich korrekt angemeldet hat (siehe Session-Mechanismus in PHP!), um im Erfolgsfalle die Datei "/geheim/datei1.dat" per readfile() an den Browser auszugeben.

          Analog könnte man auch den Unterordner dieses Beispiels benutzen:
          http://example.org/download.php?f=unterordner/bild_a.gif

          WICHTIG!!! Du müsstest unbedingt sicherstellen, dass der User nicht mit so bösen Requests kommt, dass er aus völlig falschen Ordnern Dateien abrufen kann! Beispiel: http://example.org/download.php?f=../anderer_ordner/datei_xyz.php
          Hier hättest Du ein massives Sicherheitsrisiko!

          Klarer geworden, was ich meine?

          Liebe Grüße aus Ellwangen,

          Felix Riesterer.

          --
          ie:% br:> fl:| va:) ls:[ fo:) rl:° n4:? de:> ss:| ch:? js:) mo:} zu:)
          1. Moin Moin!

            Nun testen wir einen Browseraufruf direkt: http://example.org/geheim/datei1.dat
            Dieser Request wird mit einer Nachfrage nach Benutzernamen und Passwort quittiert, da wir in der .htaccess definiert haben, dass man eine gültige Authentifizierung braucht, um dort erfolgreich eine Datei zu erhalten - aber da es keine gültige Authentifizierung gibt, endet der Zugriff im Nichts.

            Viel eleganter und ohne die sinnlose Passwort-Abfrage wäre ein "Order deny, allow", "Deny from all". Das ergibt sofort ein 403 Permission Denied. Wahlweise packt man die zu schützenden Dateien in ein Verzeichnis AUSSERHALB des vom Webserver abgedeckten Verzeichnisbaums (was bei manchen Hostern leider nicht geht).

            Alexander

            --
            Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
            1. Viel eleganter und ohne die sinnlose Passwort-Abfrage wäre ein "Order deny, allow", "Deny from all". Das ergibt sofort ein 403 Permission Denied. Wahlweise packt man die zu schützenden Dateien in ein Verzeichnis AUSSERHALB des vom Webserver abgedeckten Verzeichnisbaums (was bei manchen Hostern leider nicht geht).

              Alexander

              Ich habs mal folgendes probiert:
              (.htaccess im ordner "geheim")

              Order deny, allow
                Deny from all
                Allow from .mydomain.com

              Das sperrt aber leider wie bei meinem ersten Versuch über chmod alle Zugriffe.
              Das Skript download.php im root Verzeichnis kann aber auch nicht wie erwartet Inhalte aus dem Verzeichnis "geheim" ausgeben. Wo liegt mein Denkfehler?

              1. Also um das nochmal klar auszudrücken, per header() kann die download.php schon Dateien im "geheim" Verzeichnis ausgeben, aber ich möchte Grafiken aus diesem Ordner von einem php-Skript ganz normal per

                echo "<img src='geheim/pic.jpg'>"

                ausgeben können. Das muss doch möglich sein?

                1. Hallo Frank,

                  Also um das nochmal klar auszudrücken, per header() kann die download.php schon Dateien im "geheim" Verzeichnis ausgeben, aber ich möchte Grafiken aus diesem Ordner von einem php-Skript ganz normal per

                  echo "<img src='geheim/pic.jpg'>"

                  ausgeben können. Das muss doch möglich sein?

                  Nein. Es ist Sinn und Zweck der Übung, das _nicht_ zu tun. Wenn das möglich ist, dann bekommt man diese Grafik auch ohne Session. Dann ist diese Grafik _nicht_ geschützt. Die Idee beim Schutz dieser Art ist, dass die Grafik eben durch ein Skript zurückgegeben wird (d.h. Senden entsprechender Header mit anschließender Ausgabe des Inhalts der "realen" Datei. Da das Skript zunächst das Vorhandensein einer Session prüft, kann ohne Session kein Zugriff auf die Grafik erfolgen.

                  Der Ablageort der geschützten Datei ist idealerweise außerhalb der document_root des Servers, so dass die Grafik direkt überhaupt nicht vom Server ausgeliefert werden kann. Hat man keinen Zugriff auf solche Bereiche aber dafür die Möglichkeit, verzeichnisspezifische Konfigurationsdateien (sprich .htaccess) zu nutzen, dann kann man den hier vorgeschlagenen Weg gehen.

                  Aber immer erfolgt die Auslieferung der Grafik über ein Skript, niemals direkt.

                  Freundliche Grüße

                  Vinzenz

                  1. ok,schade.
                    Da bin ich aber von dem System ganz schön enttäuscht, ich hatte wirklich gedacht, dass php-Dateien als "User" auftreten könnten.

                    1. Lieber Frank,

                      Da bin ich aber von dem System ganz schön enttäuscht, ich hatte wirklich gedacht, dass php-Dateien als "User" auftreten könnten.

                      ich fürchte, Du weißt nicht, was Du willst!! In Deinem OP schreibst Du:

                      Ich möchte Downloads aus einem geschützten Ordner ausgeben, so dass nur (per Session) registrierte Nutzer Zugriff darauf haben.

                      Nun haben wir Dir einen Lösungsweg vorgeschlagen, der sinnvoll und relativ leicht zu bewerkstelligen ist, und nun schreibst Du etwas von "enttäuscht" und von "PHP-Dateien als 'User' auftreten" (?!???)...

                      Was ist denn bitteschön so schwer daran, eine Grafik so zu referenzieren???
                      <img src="/download.php?f=unterordner/bild_b.gif" alt="geschütztes Bild" />

                      Liebe Grüße aus Ellwangen,

                      Felix Riesterer.

                      --
                      ie:% br:> fl:| va:) ls:[ fo:) rl:° n4:? de:> ss:| ch:? js:) mo:} zu:)
                      1. Lieber Frank,

                        ich fürchte, Du weißt nicht, was Du willst!! In Deinem OP schreibst Du:

                        Ich möchte Downloads aus einem geschützten Ordner ausgeben, so dass nur (per Session) registrierte Nutzer Zugriff darauf haben.

                        Ja, und das funktioniert ja dank euch jetzt auch. Ich habe vielleicht nicht klar genug gemacht, dass das mit der Grafik ein anderes Problem ist.

                        Was ist denn bitteschön so schwer daran, eine Grafik so zu referenzieren???
                        <img src="/download.php?f=unterordner/bild_b.gif" alt="geschütztes Bild" />

                        Daran ist natürlich gar nichts schwer, nur war mir absolut nicht klar, dass man das so machen kann. Immerhin wird als Quelle keine Grafik angegeben, das ist mir total neu.

                        Ich bitte meine Unwissenheit zu entschuldigen, aber manche Gedanken sind für mich als Anfänger ohne ein Beispiel wie das von Dir oben genannte nicht sofort zu verstehen. Jedenfalls kann man meine Probleme jetzt als gelöst betrachten, herzlichen Dank nochmal.

                        1. Hi Frank,

                          <img src="/download.php?f=unterordner/bild_b.gif" alt="geschütztes Bild" />

                          Daran ist natürlich gar nichts schwer, nur war mir absolut nicht klar, dass man das so machen kann. Immerhin wird als Quelle keine Grafik angegeben, das ist mir total neu.

                          der Webserver macht ja nichts anderes, wenn du eine "echte" GIF-Datei direkt auf dem Server referenzierst, also in der Art <img src="/bild_b.gif" alt="Bild" />. Er sendet einen HTTP-Header und liefert den Inhalt der Datei aus. Das kannst du dir selbst ansehen, wenn du dir z.B. das SELFHTML-Logo über einen HTTP-Trace-Service ansiehst: SELFHTML-Logo bei Web-Sniffer. Anhand des Content-Type-Headers weiß der Client, wie er die Ressource zu verarbeiten hat.

                          Dieses Verhalten (Senden des passenden Content-Type-Headers (header() und des Dateiinhalts (z.B. readfile()) muss dein Script download.php nachmachen, dann wird der Browser die Grafik auch korrekt verarbeiten.

                          Gruß,
                          Andreas.

          2. Vielen Dank nochmal für die superausfürliche Erklärung, Felix!
            Dass ich das über .htaccess machen muss, war mir bisher gar nicht klar.
            Da werde ich mich nun wohl doch mal reinlesen müssen; *seufz*
            Danke auch an Alexander, ich hab zwar noch keine Ahnung, was das mit dem deny und so auf sich hat, aber ich denke ich finde es noch raus...