Kurz gesagt: Bei der PHP-Upload-Sicherheit entscheidet nicht eine einzelne Prüfung, sondern die Kombination: Dateityp serverseitig prüfen, Dateien umbenennen, außerhalb des Webroots ablegen und im Upload-Ordner keine Skriptausführung zulassen. Ein ungeprüftes Upload-Formular ist einer der klassischen Wege, über den Angreifer eine Hintertür auf einen Webserver bringen.
Warum Upload-Formulare ein bevorzugtes Einfallstor sind
Jedes Formular, das Dateien annimmt, schreibt fremde Daten auf Ihren Server. Dazu gehören Bewerbungsformulare, Kundenportale, Bildergalerien, Supportbereiche und viele Erweiterungen von CMS und Shops. Gelingt es einem Angreifer, dort eine Datei abzulegen, die der Webserver anschließend als PHP ausführt, hat er eine dauerhafte Hintertür.
Für die Geschäftsführung heißt das: Ein harmlos wirkender Upload für Lebensläufe kann im schlimmsten Fall zur Übernahme des gesamten Webservers führen. Außerdem liegen in Upload-Ordnern oft personenbezogene Daten, die bei einer falschen Konfiguration öffentlich abrufbar sind.
Für Admins und Entwickler heißt das: Anwendungscode und Serverkonfiguration müssen zusammenpassen. Eine Schwäche auf einer Ebene wird oft von der anderen aufgefangen, wenn beide sauber umgesetzt sind. Wie Sie bereits eingeschleuste Dateien finden, beschreibt der Ratgeber Webshells erkennen und entfernen.
Die Grundregeln für PHP-Upload-Sicherheit
Die folgenden Regeln entsprechen den Empfehlungen der OWASP Cheat Sheets und haben sich im Betrieb bewährt:
- Erlaubnisliste statt Sperrliste: Nur ausdrücklich erlaubte Dateitypen annehmen, zum Beispiel PDF, JPG und PNG. Sperrlisten für einzelne Endungen lassen sich über Varianten und Doppelendungen umgehen.
- Inhalt prüfen, nicht nur die Endung: Den tatsächlichen Typ serverseitig mit
finfoermitteln. Der MIME-Typ in$_FILESist eine Angabe des Absenders und nicht vertrauenswürdig. - Neuer Dateiname: Den Originalnamen nie als Dateinamen verwenden. Einen zufälligen Namen erzeugen und die Endung aus der Erlaubnisliste anhängen.
- Ablage außerhalb des Webroots: Dateien so speichern, dass sie nicht direkt per URL erreichbar sind. Ausgeliefert wird über ein Skript mit Berechtigungsprüfung.
- Größe begrenzen: In der Anwendung, in PHP und im Webserver.
- Keine Ausführung: Müssen Uploads doch im Webroot liegen, die Skriptausführung in diesem Verzeichnis abschalten.
- Prüfung nur auf dem Server: JavaScript-Prüfungen im Browser verbessern die Bedienung, schützen aber nicht.
Ein sicheres Beispiel mit finfo und zufälligem Namen
Das folgende Beispiel zeigt den Kern einer sicheren Verarbeitung. Es ist bewusst knapp und muss in Ihre Anwendung eingebettet werden, also mit Anmeldung, Berechtigungsprüfung, CSRF-Schutz und Protokollierung.
<?php
declare(strict_types=1);
const MAX_BYTES = 5 * 1024 * 1024; // 5 MB
const ERLAUBT = [
'application/pdf' => 'pdf',
'image/jpeg' => 'jpg',
'image/png' => 'png',
];
const ZIELORDNER = '/var/www/daten/uploads'; // außerhalb des Webroots
$datei = $_FILES['dokument'] ?? null;
if (!is_array($datei) || $datei['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
exit('Upload fehlgeschlagen.');
}
if ($datei['size'] > MAX_BYTES || !is_uploaded_file($datei['tmp_name'])) {
http_response_code(400);
exit('Datei ungültig.');
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$typ = $finfo->file($datei['tmp_name']);
if (!isset(ERLAUBT[$typ])) {
http_response_code(415);
exit('Dateityp nicht erlaubt.');
}
$name = bin2hex(random_bytes(16)) . '.' . ERLAUBT[$typ];
$ziel = ZIELORDNER . '/' . $name;
if (!move_uploaded_file($datei['tmp_name'], $ziel)) {
http_response_code(500);
exit('Speichern fehlgeschlagen.');
}
chmod($ziel, 0640);
// Den Originalnamen nur als Text in der Datenbank speichern
// und bei jeder Ausgabe mit htmlspecialchars() escapen.
Bei Bildern lohnt ein weiterer Schritt: das Bild mit GD oder Imagick neu kodieren und dabei die Metadaten verwerfen. Das ersetzt die anderen Prüfungen nicht, senkt aber das Restrisiko durch eingebettete Fremddaten.
Für Dokumente aus unbekannten Quellen, etwa Bewerbungen oder Reklamationsfotos, ist zusätzlich ein Virenscan vor der Weitergabe sinnvoll. Er schützt weniger den Webserver als die Mitarbeiter, die die Dateien später auf ihren Arbeitsrechnern öffnen. Protokollieren Sie außerdem jeden Upload mit Zeit, Konto, Dateigröße und erkanntem Typ, damit sich ungewöhnliche Muster später nachvollziehen lassen.
Bei CMS und Shops bringen die eingebauten Medienbibliotheken meist eigene Prüfungen mit. Riskanter sind Formular-Erweiterungen, Import-Funktionen und selbst programmierte Zusatzseiten, die Dateien an der Bibliothek vorbei speichern. Gerade diese Stellen sollten Sie bei einer Prüfung zuerst ansehen.
Liefern Sie Dateien aus dem geschützten Ordner mit Content-Disposition: attachment und X-Content-Type-Options: nosniff aus. Dann zeigt der Browser problematische Inhalte nicht direkt an, sondern bietet sie als Download an.
PHP-Ausführung im Upload-Ordner abschalten
Selbst wenn eine Datei mit falschem Inhalt durchrutscht, darf der Webserver sie nicht als Skript ausführen. Diese zweite Schutzschicht gehört auf jeden Server, der Uploads im Webroot speichert.
Für Nginx mit PHP-FPM:
location ^~ /uploads/ {
location ~* \.(php|phtml|phar)$ {
deny all;
}
}
Für Apache, am besten direkt im VirtualHost statt in einer .htaccess, damit die Regel nicht von der Anwendung überschrieben werden kann:
<Directory "/var/www/html/uploads">
AllowOverride None
<FilesMatch "\.(php|phtml|phar)$">
Require all denied
</FilesMatch>
</Directory>
Läuft PHP als Apache-Modul (mod_php), können Sie im selben Block zusätzlich php_admin_flag engine off setzen. Prüfen Sie nach der Änderung mit einer harmlosen Testdatei, die nur einen Text ausgibt, dass der Server eine Anfrage darauf mit 403 beantwortet.
Größengrenzen und der Bezug zu PHP2Shell
Uploads hängen eng mit den PHP-Einstellungen post_max_size und upload_max_filesize zusammen. Im Zuge von PHP2Shell empfiehlt php.net als Übergangsmaßnahme, post_max_size auf 8M zu setzen, wenn keine Uploads nötig sind, oder auf 32M mit Uploads, und upload_max_filesize darunter zu halten. Das ist kein vollständiger Schutz und ersetzt nicht das Update auf eine behobene PHP-Version.
; php.ini oder FPM-Pool (php_admin_value), danach alle Worker neu starten
file_uploads = On
post_max_size = 32M
upload_max_filesize = 20M
max_file_uploads = 5
Braucht eine Website gar keine Uploads, ist file_uploads = Off die sauberste Lösung. Welche Einstellungen bis zum Patch sinnvoll sind, steht unter Sofortmaßnahmen ohne Patch. Passende Rechte für Upload-Verzeichnisse beschreibt Dateirechte auf dem Webserver.
Typische Fehler in Upload-Funktionen
Bei der Durchsicht bestehender Anwendungen tauchen immer wieder dieselben Muster auf. Prüfen Sie Ihre Formulare gezielt auf diese Punkte:
- Die Endung wird nur teilweise geprüft, sodass Dateinamen mit mehreren Endungen durchrutschen.
- Der Originalname aus dem Formular landet im Speicherpfad. Das öffnet Wege für Pfadmanipulation und Überschreiben bestehender Dateien.
- Uploads liegen öffentlich mit vorhersehbaren Namen. Bei Bewerbungen oder Verträgen ist das auch ein Datenschutzproblem.
- Der Upload-Ordner ist für den Webserver beschreibbar und gleichzeitig ausführbar.
- Alte Upload-Erweiterungen oder Formular-Plugins werden nicht aktualisiert, obwohl der Hersteller Sicherheitsupdates veröffentlicht hat.
- Fehler werden nicht protokolliert. Abgelehnte Uploads sind aber ein frühes Signal für gezielte Versuche.
Wie hilft PHP SOS konkret?
- Wir prüfen Ihre Upload-Funktionen im Code und in der Serverkonfiguration und benennen konkrete Schwachstellen mit Priorität.
- Wir setzen die Ausführungssperre im Upload-Ordner, passende Rechte und Größengrenzen im Rahmen der Server-Härtung.
- Bei Verdacht durchsuchen wir Upload-Verzeichnisse nach eingeschleusten Skripten und bereinigen sie über unsere Malware-Entfernung.
- Auf Wunsch lassen wir die Umsetzung durch einen Penetrationstest unabhängig gegenprüfen.
Häufige Fragen
Reicht es, die Dateiendung beim Upload zu prüfen?
Nein. Die Endung und der vom Browser gemeldete MIME-Typ stammen vom Absender und lassen sich frei wählen. Prüfen Sie den Inhalt serverseitig, vergeben Sie einen neuen Dateinamen und sperren Sie die Skriptausführung im Upload-Verzeichnis.
Wo sollten hochgeladene Dateien gespeichert werden?
Am besten außerhalb des öffentlichen Webverzeichnisses. Die Auslieferung übernimmt dann ein Skript, das vorher prüft, ob der Nutzer die Datei sehen darf. So sind Dateien weder direkt aufrufbar noch ausführbar.
Sind SVG-Dateien als Bilder unbedenklich?
Nicht automatisch. SVG ist XML und kann aktive Inhalte enthalten. Erlauben Sie SVG nur, wenn Sie die Dateien bereinigen oder ausschließlich als Download mit passendem Content-Disposition-Header ausliefern.
Hat die Upload-Größe etwas mit PHP2Shell zu tun?
Ja, indirekt. php.net empfiehlt als Übergangsmaßnahme niedrigere Werte für post_max_size und upload_max_filesize. Das senkt das Risiko, ersetzt aber nicht das Update auf eine behobene PHP-Version und den Neustart aller Worker.