Sicherheitsbedenken bei Datei-Uploads auf dem eigenen Server

Ja, Skepsis bei Datei-Uploads ist absolut berechtigt. Eine Dateiendung wie .jpg, .pdf, .docx, .zip oder ähnliche garantiert nicht, dass eine Datei ungefährlich ist. Die Endung ist zunächst nur ein Name. Der tatsächliche Inhalt kann trotzdem manipuliert, schädlich oder unerwartet sein.

Wichtig: Gefährlich ist nicht nur die Datei selbst, sondern auch, wo sie gespeichert wird, ob sie vom Server ausgeführt werden kann und ob sie später von jemandem geöffnet, entpackt oder weiterverarbeitet wird.

1. Die Dateiendung allein reicht nicht

Ein Angreifer kann eine Datei harmlos benennen, zum Beispiel:

rechnung.pdf
bild.jpg
archiv.zip

Trotzdem kann der Inhalt der Datei problematisch sein. Deshalb sollte man sich bei Uploads niemals allein auf die Dateiendung verlassen. Sinnvoll ist eine Kombination aus erlaubten Dateiendungen, Prüfung des tatsächlichen Dateityps und einer sicheren Speicherung.

Auch OWASP empfiehlt bei Datei-Uploads ausdrücklich zusätzliche Prüfungen und Schutzmaßnahmen: OWASP File Upload Cheat Sheet

2. Office-Dateien können Makros enthalten

Besonders kritisch sind alte Office-Dateien und Office-Dateien mit Makros, zum Beispiel:

doc
xls
ppt
docm
xlsm
pptm

Gerade die alten Formate .doc, .xls und .ppt würde ich in einem allgemeinen Kunden-Upload nur erlauben, wenn es wirklich notwendig ist. Für einfache Kundenunterlagen sind sie meist nicht erforderlich.

Eine deutlich vorsichtigere Liste erlaubter Dateiendungen wäre zum Beispiel:

$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf', 'txt'];

Wenn ZIP-Dateien wirklich benötigt werden, könnte man die Liste erweitern:

$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf', 'txt', 'zip'];

Noch sicherer wäre für normale Kundenunterlagen:

$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf'];

3. ZIP und RAR sind besonders heikel

ZIP- und RAR-Dateien können praktisch alles enthalten, zum Beispiel:

.exe
.php
.js
.bat
.cmd
Office-Dateien mit Makros
verschachtelte Archive
sehr große entpackte Datenmengen

Ein ZIP-Archiv ist nicht automatisch gefährlich, aber es ist eine Art Blackbox. Wenn ZIP oder RAR erlaubt werden, sollte man diese Dateien niemals automatisch entpacken und schon gar nicht deren Inhalt direkt auf dem Server bereitstellen.

RAR-Dateien würde ich in einem einfachen Upload-Formular eher weglassen.

4. Der Upload-Ordner darf nichts ausführen

Der wichtigste Punkt ist der Speicherort. In vielen PHP-Scripten sieht man zum Beispiel:

define('UPLOAD_DIR', __DIR__ . '/uploads/');

Wenn dieser Ordner innerhalb der Webseite liegt und über den Browser erreichbar ist, zum Beispiel:

https://www.deine-domain.de/uploads/datei.pdf

dann sollte dort niemals PHP, HTML, CGI oder anderer Scriptcode ausführbar sein. Die sicherste Lösung wäre, Uploads außerhalb des öffentlich erreichbaren Webbereichs zu speichern. Wenn das nicht möglich ist, sollte der Upload-Ordner zusätzlich abgesichert werden.

Beispiel für eine .htaccess im Ordner uploads

Options -Indexes

<FilesMatch "\.(php|php3|php4|php5|php7|php8|phtml|phar|cgi|pl|py|sh|html|htm|js)$">
    Require all denied
</FilesMatch>

RemoveHandler .php .php3 .php4 .php5 .php7 .php8 .phtml .phar .cgi .pl .py .sh
RemoveType .php .php3 .php4 .php5 .php7 .php8 .phtml .phar .cgi .pl .py .sh
php_flag engine off

Nicht jeder Hoster erlaubt jede dieser Direktiven. Falls bei einem Hoster durch php_flag engine off ein Serverfehler entsteht, sollte diese Zeile entfernt werden.

5. Dateien auf dem Server umbenennen

Es ist gut, hochgeladene Dateien nicht unter dem Originalnamen zu speichern. Sinnvoll ist zum Beispiel eine serverseitig erzeugte Datei:

$cleanName = preg_replace('/[^a-zA-Z0-9_-]/', '_', $kundeName);
$serverFilename = time() . '_' . $cleanName . '.' . $ext;

Dadurch werden Sonderzeichen und problematische Original-Dateinamen entschärft. Der ursprüngliche Dateiname sollte nicht direkt als Speichername auf dem Server verwendet werden.

Auch die PHP-Funktion move_uploaded_file() ist sinnvoll, weil sie prüft, ob die Datei tatsächlich über einen HTTP-POST-Upload übertragen wurde: PHP-Dokumentation zu move_uploaded_file()

6. Maximale Dateigröße begrenzen

Ohne Größenbegrenzung kann jemand den Webspace mit sehr großen Dateien füllen. Deshalb sollte eine maximale Upload-Größe festgelegt werden:

$maxFileSize = 10 * 1024 * 1024; // 10 MB

if ($file['size'] > $maxFileSize) {
    $status = 'danger';
    $message = 'Fehler: Die Datei ist zu groß. Maximal erlaubt sind 10 MB.';
}

7. MIME-Type zusätzlich prüfen

Die Prüfung des MIME-Types ist nicht perfekt, aber sie ist deutlich besser als nur die Prüfung der Dateiendung:

$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->file($file['tmp_name']);

$allowedMimeTypes = [
    'jpg'  => ['image/jpeg'],
    'jpeg' => ['image/jpeg'],
    'png'  => ['image/png'],
    'pdf'  => ['application/pdf'],
    'txt'  => ['text/plain'],
    'zip'  => ['application/zip', 'application/x-zip-compressed']
];

if (!isset($allowedMimeTypes[$ext]) || !in_array($mimeType, $allowedMimeTypes[$ext], true)) {
    $status = 'danger';
    $message = 'Fehler: Der tatsächliche Dateityp passt nicht zur Dateiendung.';
}

Diese Prüfung ersetzt keine vollständige Sicherheitslösung, ist aber ein sinnvoller zusätzlicher Schutz.

8. Upload-Ordner nicht öffentlich auflisten

Der Upload-Ordner sollte nicht als Verzeichnis im Browser auflistbar sein. Dafür kann man in der .htaccess verwenden:

Options -Indexes

Zusätzlich kann eine leere index.html in den Upload-Ordner gelegt werden.

9. Die Upload-PIN sollte nicht zu einfach sein

Eine einfache Beispiel-PIN wie:

define('VALID_PIN', '1234');

ist für Tests in Ordnung, aber nicht für den echten Betrieb. Besser wäre eine längere, zufällige PIN oder ein Passwort:

define('VALID_PIN', 'T7k9-Upload-2026-xQ4');

10. Einschätzung für ein einfaches Kunden-Upload-Script

Für einen kleinen, privaten Kunden-Upload mit geheimer PIN kann ein solches Script als Grundidee brauchbar sein. In der ursprünglichen Form wäre es aber zu offen, wenn sehr viele Dateitypen erlaubt sind und der Upload-Ordner öffentlich innerhalb der Webseite liegt.

Die wichtigsten Verbesserungen:

  1. Den Upload-Ordner per .htaccess so absichern, dass dort nichts ausgeführt werden kann.
  2. Die erlaubten Dateitypen stark reduzieren.
  3. Alte Office-Dateien und RAR-Dateien möglichst nicht erlauben.
  4. Eine maximale Dateigröße einbauen.
  5. Den MIME-Type zusätzlich prüfen.
  6. ZIP-Dateien nur erlauben, wenn sie wirklich benötigt werden.
  7. Hochgeladene Dateien niemals automatisch öffnen, entpacken oder direkt ausführen lassen.

Empfohlene Dateitypen

Für eine vorsichtige, aber noch praktikable Variante:

$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf', 'txt', 'zip'];

Für eine besonders sichere Variante:

$allowedExtensions = ['jpg', 'jpeg', 'png', 'pdf'];
Fazit: Ein hochgeladenes PDF, ZIP oder Office-Dokument infiziert den PHP-Server normalerweise nicht einfach dadurch, dass es gespeichert wird. Gefährlich wird es vor allem dann, wenn die Datei später geöffnet, entpackt, weiterverteilt, im Browser direkt ausgeliefert oder vom Server ausgeführt wird.