Aktuelle Lage: PHP2Shell. Zwei kritische PHP-Lücken (CVSS 9.2) erlauben Schadcode ohne Anmeldung. Patches seit 21.10.2026.Was jetzt zu tun ist →

Wie PHP Datei-Uploads verarbeitet

Zwischen dem Klick auf Hochladen und Ihrem Code liegt eine ganze Verarbeitungskette in PHP. Diese Seite erklärt multipart/form-data, temporäre Dateien, $_FILES, Grenzwerte und den Bezug zu PHP2Shell.

Stand 5 Min. LesezeitPHP-Wissen

Kurz gesagt: Wenn ein Browser eine Datei hochlädt, schickt er eine POST-Anfrage im Format multipart/form-data, in der Formularfelder und Dateiinhalte als einzelne Teile hintereinander stehen. PHP zerlegt diese Anfrage selbst, schreibt jeden Dateiteil in eine temporäre Datei im upload_tmp_dir, trägt die Angaben in $_FILES ein und startet erst dann Ihr Skript. Das Skript verschiebt die Datei mit move_uploaded_file(); alles, was nicht verschoben wird, löscht PHP am Ende der Anfrage.

Der Weg einer Datei vom Browser bis ins Skript

Für Entscheider ist vor allem eines wichtig: Ein Upload ist kein Vorgang der Anwendung allein. Ein großer Teil der Arbeit passiert im PHP-Interpreter, bevor der Code von CMS oder Shop überhaupt beteiligt ist. Deshalb betreffen Fehler in diesem Teil von PHP jede Website, ob sie ein Upload-Formular hat oder nicht.

Der Ablauf im Überblick:

  1. Das Formular mit method="POST" und enctype="multipart/form-data" wird abgeschickt.
  2. Der Webserver nimmt die Anfrage an und reicht sie an PHP weiter, je nach Betriebsart über PHP-FPM, mod_php oder eine andere Schnittstelle.
  3. PHP prüft Grenzwerte wie post_max_size und zerlegt den Inhalt anhand einer Trennzeichenfolge, der sogenannten Boundary.
  4. Normale Felder landen in $_POST, Dateiteile werden in temporäre Dateien geschrieben und in $_FILES beschrieben.
  5. Erst jetzt startet das Skript, prüft die Datei und verschiebt sie an ihren Zielort.
  6. Am Ende der Anfrage löscht PHP alle temporären Dateien, die nicht verschoben wurden.

multipart/form-data: der Aufbau einer Upload-Anfrage

Eine Upload-Anfrage ist reiner Text mit eingebetteten Dateidaten. Der Header Content-Type nennt die Boundary, und im Inhalt trennt diese Zeichenfolge die einzelnen Teile. Jeder Teil hat eigene Kopfzeilen mit Feldname und gegebenenfalls Dateiname. Ein stark gekürztes, harmloses Beispiel:

POST /bewerbung HTTP/1.1
Content-Type: multipart/form-data; boundary=----trenner42

------trenner42
Content-Disposition: form-data; name="vorname"

Erika
------trenner42
Content-Disposition: form-data; name="lebenslauf"; filename="lebenslauf.pdf"
Content-Type: application/pdf

%PDF-1.7 ... Dateiinhalt ...
------trenner42--

Alle Angaben in diesem Text stammen vom Absender, auch Dateiname und Content-Type des Teils. PHP übernimmt sie in $_FILES, prüft sie aber nicht auf Plausibilität.

Das $_FILES-Array

Für ein Feld mit dem Namen lebenslauf enthält $_FILES['lebenslauf'] laut PHP-Handbuch diese Schlüssel:

Schlüssel Inhalt Vertrauenswürdig?
name ursprünglicher Dateiname beim Absender nein
full_path vom Browser gemeldeter Pfad, seit PHP 8.1.0 nein
type MIME-Typ laut Browser, von PHP nicht geprüft nein
size Größe in Bytes von PHP ermittelt
tmp_name Pfad der temporären Datei auf dem Server von PHP vergeben
error Fehlercode der Upload-Verarbeitung von PHP gesetzt

Bei Feldern mit eckigen Klammern im Namen, etwa bilder[], enthält jeder Schlüssel ein Array mit einem Eintrag je Datei. Die Anwendung durchläuft dann zum Beispiel $_FILES['bilder']['error'] und greift über denselben Index auf tmp_name und name zu.

Ein Formular kann zusätzlich ein verstecktes Feld MAX_FILE_SIZE vor dem Dateifeld senden. Laut Handbuch ist das nur ein Hinweis an den Browser, der sich leicht umgehen lässt. Verlassen können Sie sich ausschließlich auf die Grenzen, die der Server durchsetzt: in PHP, im Webserver und in der Anwendung selbst.

Grenzwerte: die php.ini-Direktiven für Uploads

Diese Direktiven steuern die Upload-Verarbeitung. Die Standardwerte stammen aus dem PHP-Handbuch und gelten, wenn keine php.ini sie überschreibt.

Direktive Standard Änderbar in
file_uploads 1 nur Systemkonfiguration
upload_tmp_dir leer (System-Temp) nur Systemkonfiguration
upload_max_filesize 2M php.ini, Pool, Verzeichniskonfiguration
post_max_size 8M php.ini, Pool, Verzeichniskonfiguration
max_file_uploads 20 php.ini, Pool, Verzeichniskonfiguration
enable_post_data_reading 1 php.ini, Pool, Verzeichniskonfiguration

post_max_size muss größer sein als upload_max_filesize, und memory_limit sollte laut Handbuch in der Regel größer sein als post_max_size. Ist upload_tmp_dir nicht beschreibbar, weicht PHP auf das Temp-Verzeichnis des Systems aus. Mit open_basedir muss das Temp-Verzeichnis erlaubt sein, sonst schlagen Uploads fehl. Praxiswerte und deren Folgen beschreibt der Beitrag post_max_size und upload_max_filesize sinnvoll begrenzen.

Fehlercodes richtig auswerten

Das Feld error sagt, was bei der Verarbeitung passiert ist. Die Konstanten reichen von UPLOAD_ERR_OK (0) über UPLOAD_ERR_INI_SIZE (1), UPLOAD_ERR_FORM_SIZE (2), UPLOAD_ERR_PARTIAL (3) und UPLOAD_ERR_NO_FILE (4) bis UPLOAD_ERR_NO_TMP_DIR (6), UPLOAD_ERR_CANT_WRITE (7) und UPLOAD_ERR_EXTENSION (8).

<?php
declare(strict_types=1);

$datei = $_FILES['lebenslauf'] ?? null;
if ($datei === null) {
    exit('Keine Upload-Daten, eventuell wurde post_max_size überschritten.');
}

$meldung = match ($datei['error']) {
    UPLOAD_ERR_OK => null,
    UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'Datei zu groß.',
    UPLOAD_ERR_NO_FILE => 'Keine Datei gewählt.',
    default => 'Upload fehlgeschlagen.',
};

if ($meldung === null && is_uploaded_file($datei['tmp_name'])) {
    $ziel = '/srv/app/uploads/' . bin2hex(random_bytes(16)) . '.pdf';
    move_uploaded_file($datei['tmp_name'], $ziel);
}

Das Beispiel zeigt nur die Mechanik. Prüfung des Dateityps, Ablage außerhalb des Webroots und das Sperren der Skriptausführung beschreibt der Ratgeber Datei-Uploads in PHP sicher umsetzen.

Uploads und PHP2Shell: Was die Übergangsmaßnahmen bedeuten

Die PHP2Shell-Lücken CVE-2026-109618 und CVE-2026-109619 betreffen die Verarbeitung rund um die Anfrage im PHP-Interpreter. Deshalb nennt php.net als Übergangsmaßnahmen unter anderem post_max_size = 8M (ohne Uploads) oder 32M (mit Uploads), upload_max_filesize darunter, sowie session.upload_progress.enabled=0. Diese Werte gehören in php.ini, FPM-Pool oder Serverkonfiguration, danach müssen alle Worker neu starten.

Fachautoren schlagen zusätzlich vor, multipart/form-data-Anfragen außerhalb echter Upload-Pfade am Proxy oder in der WAF zu blockieren. Das ist eine inoffizielle Maßnahme mit dem Risiko, dass Formulare brechen; die Abwägung beschreibt der Beitrag Upload-Anfragen am Rand filtern. Keine dieser Maßnahmen ersetzt das Update auf PHP 8.5.12, 8.4.27, 8.3.36 oder 8.2.35.

Wie hilft PHP SOS konkret?

  • Wir ermitteln, welche Ihrer Websites tatsächlich Uploads brauchen und auf welchen Pfaden.
  • Wir setzen die Grenzwerte und Übergangsmaßnahmen je Pool, starten alle Worker neu und testen die Formulare.
  • Wir prüfen Upload-Verzeichnisse auf ausführbare Dateien und sperren die Skriptausführung im Rahmen der Server-Härtung.
  • Wir spielen das PHP-Update ein und dokumentieren den Patchstand je Website.

Häufige Fragen

Wo speichert PHP hochgeladene Dateien zuerst?

In einem temporären Verzeichnis. Standardmäßig ist das das temporäre Verzeichnis des Systems; mit upload_tmp_dir lässt es sich ändern. Wird die Datei im Skript nicht verschoben, löscht PHP sie am Ende der Anfrage.

Kann ich dem Feld type in $_FILES vertrauen?

Nein. Den MIME-Typ meldet der Browser, PHP prüft ihn laut Handbuch nicht. Auch name und full_path stammen vom Absender. Den tatsächlichen Dateityp ermitteln Sie serverseitig anhand des Inhalts.

Warum ist $_FILES leer, obwohl eine Datei gesendet wurde?

Meist war die gesamte Anfrage größer als post_max_size. Dann leert PHP $_POST und $_FILES vollständig, ohne Fehlercode im Array. Weitere Ursachen sind ein fehlendes enctype im Formular oder abgeschaltete file_uploads.

Hilft es gegen PHP2Shell, Uploads in der Anwendung zu verbieten?

Nein. Die Verarbeitung der Anfragedaten passiert in PHP, bevor Anwendungscode läuft. Wirksam sind das PHP-Update und der Neustart aller Worker; engere Grenzwerte sind nur eine Übergangsmaßnahme.

Was ist der Unterschied zwischen upload_max_filesize und post_max_size?

upload_max_filesize begrenzt jede einzelne Datei, post_max_size den gesamten Inhalt der Anfrage einschließlich aller Dateien und Felder. post_max_size muss deshalb größer sein als upload_max_filesize.

Quellen

Sofort-Kontakt

Jede Stunde zählt. Schreiben Sie uns jetzt.

Schildern Sie kurz System, PHP-Version und Beobachtung. Wir melden uns, klären die Lage und arbeiten remote, bis der Vorfall abgeschlossen und dokumentiert ist.

Sofort-Hilfe