Raketenwilli: Sicherheit, weitere Ideen

Beitrag lesen

Aber was tust Du, wenn Dir jemand per handgezaubertem Upload-Request ../test.php als Dateiname übermittelt? Keine Ahnung ob PHP dem einen Riegel vorschiebt.

Das weiß ich: PHP tut das selbst nicht. Zumindest die Dateiendung bleibt.

Es ist nicht so schlimm, wenn Du über Zugriffsrechte sicherstellst, dass dein PHP Runtime-User nur auf den files-Ordner schreiben kann.

Ich habe da Bauchschmerzen, denn da kommt der Feind laut Plan bis zur letzten Verteidigungslinie, die da heißt, dass in einem bestimmten Ordner Dateien mit der Endung "php" (und andere: wie cgi, pl, py, ...) hoffentlich nicht bei einem Aufruf vom Webserver wie ebensolche also z.B. von PHP verarbeitet werden. Ein Ausführen-Recht ist in vielen Fällen (eben: PHP) nicht nötig. (Bei cgi oder pl zumeist schon...)

Denn wenn es so ist, hat man bei einem kleinem Fehler (e.g. unbedachte Konfigurationsänderung, unbedachte Änderung des Speicherziels) ganz schnell viele erfreute Mitbenutzer auf dem Server - wenn die „test.php“ z.B. eine „Webshell“ ist und der ursprüngliche Mailversender erraten kann, wo die dann liegt und also die URL kennt. Es dauert dann nicht lange, bis z.B. die .T..., gmail, gmx & C. keine weiteren Mails von diesem Host mehr haben wollen.

Vorgeschlagene Lösung: Es gibt tempnam(). Das verträgt auch die Angabe eines Verzeichnisses. Die Datei also mit einem sicheren Name (ergo ohne Endung) speichern und sodann die Metadaten der Datei (darunter deren Name, ggf. auch welche Benutzer der Webseite Rechte daran haben) in einer weiteren Datenbank (z.B. sqlite3) ablegen.

tempnam() löst auch das Problem, dass mehrere hoch geladene Dateien den selben Name haben.

Man kann aber sogar noch einen Schritt weiter gehen: Man nehme die Datei und hashe diese (hierfür reicht dann md5() / md5_file() oder sha1 / sha1_file() , weil es hier nicht um Kryptographie geht). Man nehme das Ergebnis als Dateiname auf dem Filesystem und vermeide das doppelte Speichern, füge aber einen weiteren Eintrag in der Datenbank mit den Metadaten hinzu. Wenn der Benutzer die Dateien auch löschen können soll muss man dann freilich aufpassen, dass die Datei selbst erst gelöscht wird, wenn der letzte Eintrag mit den zugehörigen Metadaten gelöscht wurde.

Weitere Idee: Wenn man schon dabei ist, kann man die hoch geladene Datei auch gleich mit gzip für den späteren Download (also HTTP(S)-Transport) vorbereiten und auf diesem Weg auch Speicher auf dem Server sparen :-)

Optimierung: Es können ja womöglich sehr viele Dateien werden. Wenn man wie vorgeschlagen den Hash als "physischen" Dateiname benutzt kann man für die ersten 1 bis 4 Zeichen Ordner anlegen also z.b.

  • ./0/ .. ./f/ oder
  • ./0/0/ .. ./f/f/ oder
  • ./0/0/0/0/ .. ./f/f/f/f/

(Beide hash-Verfahren geben, wiewohl als String, eigentlich eine recht große, hexadezimal formulierte Zahl aus, enhalten als zur Zeichen [0123456789abcdef]) Die Dateien dann in Abhängigkeit von den linken Stellen in diesen unterbringen:

Inhalt 'Hallo': md5('Hallo') -> 'd1bf93299de1b68e6d382c893bf1215f', käme also z.B. nach ./d/1/d1bf93299de1b68e6d382c893bf1215f

(Man kann das auch mit etwas wie <?=base64_encode(md5('Hallo',1)); machen, aber das liefert dann 0b+TKZ3hto5tOCyJO/EhXw==, dann hätte man kürzere Namen, dafür wesentlich mehr Ordner und muss das Problem klären, dass insbesondere '[+./]' darin vorkommen können…

Das geht schnell und löst Performanceprobleme, die manche Dateisysteme haben, wenn sehr viele Dateien in einem Ordner sind - weil sich dann die Dateien (bei hexadezimaler Darstellung) je nach Zahl der unterschiedenen Stellen auf 16, 256, 4096, 65536 Ordner verteilen, welche jeweils nur 16 Ordner oder halt den entsprechenden Anteil der Dateien enthalten.