Hello,
move_uploaded_file()
gehört mMn schon lange auf die depricated-Liste. Es ist noch entstanden zur Zeit der unsicheren globalen Arrays. Mit der Einführung von$_FILES
wurde aber der temporäre Name des hochgeladenen Files abgesichert. Damit wurde die Sicherheitslücke, diemove_uploaded_file()
vorher schließen sollte, beseitigt.Von welcher Sicherheitslücke sprichst du? Das Upload-Verzeichnis dient gewissermaßen als Sandbox: die Datei wird erstmal an eine Stelle geschoben, an der sie nicht viel Schaden anrichten kann.
Das gilt (im shared Hosting, Modul) nur, wenn das Uploadverzeichnis pro Domain getrennt geführt wird. Anderenfalls kann der Inhalt der Datei auch von Anderen geändert werden, solange sie dort liegt.
Es ist dann Aufgabe der Anwendung die Datei zu überprüfen (bspw. auf Mime-Type oder Viren) bevor sie aus der Sandbox an eine andere Stelle verschoben wird. Die normalen PHP Dateisystem-Funktionen haben ggf. keinen Zugriff auf das Upload-Verzeichnis, wenn es außerhalb des
open_basedir
-Verzeichnisbaums liegt. Deshalb braucht manmove_uploaded_file
als privilegierte Funktion.
Das kann ich jetzt leider nicht so schnell überprüfen. Du meinst also, dass man das upload_tmp_dir
außerhalb des open_basedir
-Bereiches und der DocumentRoot
legen kann, und der Upload trotzdem funktioniert? Und move_uploaded_file()
darf dann als einzige PHP-Dateifunktion (vermutlich nur lesend?) auf dieses Verzeichnis und das Directory dieses Verzeichnisses (schreibend, zum Löschen) zugreifen?
Daher ist die von mir vorgestellte Funktion
save_uploaded_file()
auf jeden Fall zielführender. Sie ermöglicht das TOCTTOU-freie Speichern von Dateien mit StatusrückmeldungIch habe gerade im PHP Sourcecode nachgesehen,
move_uploaded_file
benutzt unter der Haube auch den Filelock-Mechanismus. Man muss ein bisschen graben bis man auf diese beiden Zeilen stößt:srcstream = php_stream_open_wrapper_ex(src, "rb", src_flg | REPORT_ERRORS, NULL, ctx) deststream = php_stream_open_wrapper_ex(dest, "wb", REPORT_ERRORS, NULL, ctx);
Der zweite Parameter ist der File-Mode.
Darum ging es aber bei der ganzen Debatte gar nicht. Außerdem war die mögliche Sicherheitslücke, dass man aus Arglist einen fremden Dateinamen als Source untergeschoben bekommen hatte (in $HTTP_POST_FILES
), duch Einführung von $_FILES erledigt.
Also ist es möglich, auch eine eigene Move-, bzw. Save-Funktion zu erstellen, die auch sicher ist und den Zusatznutzen den von mir vorgestellten aufweist:
function save_uploaded_file($source, $target, $overwrite=0)
{
# $overwrite == 0 -> no overwrite, only create if not exists
# $overwrite == 1 -> open only if exists, overwrite, truncate!
# $overwrite == 2 -> rewrite existing or create new
# we believe that php will not produce 'lost handles' if we leave
# this function without closing them.
if (!is_int($overwrite)) return 12;
# open source
if (!$fs = fopen($source, 'rb')) return 5;
if (!flock($fs, LOCK_SH)) return 6; ## Source could not be locked;
# open target
switch ($overwrite)
{
case 0:
if (!$ft = fopen($target, 'xb')) return 3; ## assumed 'already exists'
break;
case 1:
if (!$ft = fopen($target, 'rb+')) return 2; ## assumed 'not found'
break;
case 2:
if (!$ft = fopen($target, 'wb')) return 5; ## assumed 'could not open'
break;
default:
return 12;
}
if (!flock($ft, LOCK_EX)) return 6; ## Target could not be locked;
$filesize = filesize($source);
$cont = fread($fs, $filesize);
if (strlen($cont) != $filesize) return 4;
fwrite($ft, $cont);
fclose($fs);
## new file perhaps is shorter than old one
ftruncate($ft, $filesize);
fclose($ft);
return 0;
}
Sie überschreibt nicht einfach vorhandene Dateien im Zielverzeichnis, wenn man dies nicht wünscht. Geprüft wird dies TOCTTOU-sicher. Außerdem kann man die Funktion auch genau anders herum steuern, dass sie nur bereits vorhandene Dateien überschreibt, und keine neuen anlegt. Die müssen dann ggf. gekürzt werden. Und die dritte Variante ist die, mit der move_uploaded_file()
auch arbeitet: plattmachen und/oder neu anlegen.
In meiner obigen Funktion müsste noch das namensbasierte filesize()
durch die passende handlebasierte Funktion fstat()
ausgetauscht werden, damit sie konsequent handlebasiert ist. Die gab es damals noch nicht.
und insbesondere erspart sie beim Zusammenwirken mit Datenbanken an dieser Stelle wieder Overhead. •••
Wie das?
Wenn ich Dateiinhalte nicht in der Datenbank speichern will (sollte man ja meistens nicht tun), sonden eben als Flatfiles, dann muss ich mir in der DB aber zumindest den Namen der Datei merken. Wenn der aber gleich bleiben kann, muss ich in der Datenbank nichts ändern. Ich erspare mir dort also den Schreibvorgang.
Das ist nur ein Nebensache aus der Praxis mit einer Kontaktbörse, in der die User ständig ihre Bilder austauschen. Mach also bitte jetzt keine Hauptsache daraus.
Glück Auf
Tom vom Berg
Es gibt nichts Gutes, außer man tut es!
Das Leben selbst ist der Sinn.