Kort sagt: Når en browser uploader en fil, sender den en POST-forespørgsel i formatet multipart/form-data, hvor formularfelter og filindhold står som enkelte dele efter hinanden. PHP opdeler selv denne forespørgsel, skriver hver fildel til en midlertidig fil i upload_tmp_dir, registrerer oplysningerne i $_FILES og starter først derefter jeres script. Scriptet flytter filen med move_uploaded_file(); alt, der ikke flyttes, sletter PHP ved afslutningen af forespørgslen.
Filens vej fra browseren til scriptet
For beslutningstagere er én ting vigtig: Et upload er ikke en proces, der alene hører til applikationen. En stor del af arbejdet sker i PHP-fortolkeren, før koden i CMS eller shop overhovedet er involveret. Derfor berører fejl i denne del af PHP ethvert website, uanset om det har en uploadformular eller ej.
Forløbet i overblik:
- Formularen med
method="POST"ogenctype="multipart/form-data"sendes af sted. - Webserveren modtager forespørgslen og giver den videre til PHP, alt efter driftsform via PHP-FPM, mod_php eller en anden grænseflade.
- PHP kontrollerer grænseværdier som
post_max_sizeog opdeler indholdet ved hjælp af en adskillelsesstreng, den såkaldte boundary. - Almindelige felter havner i
$_POST, fildele skrives til midlertidige filer og beskrives i$_FILES. - Først nu starter scriptet, kontrollerer filen og flytter den til dens bestemmelsessted.
- Ved afslutningen af forespørgslen sletter PHP alle midlertidige filer, der ikke blev flyttet.
multipart/form-data: opbygningen af en uploadforespørgsel
En uploadforespørgsel er ren tekst med indlejrede fildata. Headeren Content-Type angiver boundary, og i indholdet adskiller denne tegnfølge de enkelte dele. Hver del har egne headerlinjer med feltnavn og eventuelt filnavn. Et stærkt forkortet, harmløst eksempel:
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 oplysninger i denne tekst stammer fra afsenderen, også delens filnavn og Content-Type. PHP overtager dem i $_FILES, men kontrollerer ikke, om de er plausible.
$_FILES-arrayet
For et felt med navnet lebenslauf indeholder $_FILES['lebenslauf'] ifølge PHP-manualen disse nøgler:
| Nøgle | Indhold | Pålidelig? |
|---|---|---|
name |
oprindeligt filnavn hos afsenderen | nej |
full_path |
sti oplyst af browseren, siden PHP 8.1.0 | nej |
type |
MIME-type ifølge browseren, ikke kontrolleret af PHP | nej |
size |
størrelse i bytes | fastlagt af PHP |
tmp_name |
stien til den midlertidige fil på serveren | tildelt af PHP |
error |
fejlkode fra uploadbehandlingen | sat af PHP |
Ved felter med kantede parenteser i navnet, f.eks. bilder[], indeholder hver nøgle et array med én post pr. fil. Applikationen gennemløber så for eksempel $_FILES['bilder']['error'] og tilgår tmp_name og name via det samme indeks.
En formular kan desuden sende et skjult felt MAX_FILE_SIZE før filfeltet. Ifølge manualen er det kun et fingerpeg til browseren, som let kan omgås. I kan udelukkende forlade jer på de grænser, som serveren håndhæver: i PHP, i webserveren og i selve applikationen.
Grænseværdier: php.ini-direktiverne for uploads
Disse direktiver styrer uploadbehandlingen. Standardværdierne stammer fra PHP-manualen og gælder, hvis ingen php.ini overskriver dem.
| Direktiv | Standard | Kan ændres i |
|---|---|---|
file_uploads |
1 |
kun systemkonfiguration |
upload_tmp_dir |
tom (system-temp) | kun systemkonfiguration |
upload_max_filesize |
2M |
php.ini, pool, mappekonfiguration |
post_max_size |
8M |
php.ini, pool, mappekonfiguration |
max_file_uploads |
20 |
php.ini, pool, mappekonfiguration |
enable_post_data_reading |
1 |
php.ini, pool, mappekonfiguration |
post_max_size skal være større end upload_max_filesize, og memory_limit bør ifølge manualen som regel være større end post_max_size. Er upload_tmp_dir ikke skrivbar, falder PHP tilbage til systemets temp-mappe. Med open_basedir skal temp-mappen være tilladt, ellers mislykkes uploads. Praktiske værdier og deres følger beskriver indlægget Fornuftig begrænsning af post_max_size og upload_max_filesize.
Fejlkoder: sådan vurderer I dem rigtigt
Feltet error fortæller, hvad der skete under behandlingen. Konstanterne spænder fra UPLOAD_ERR_OK (0) over UPLOAD_ERR_INI_SIZE (1), UPLOAD_ERR_FORM_SIZE (2), UPLOAD_ERR_PARTIAL (3) og UPLOAD_ERR_NO_FILE (4) til UPLOAD_ERR_NO_TMP_DIR (6), UPLOAD_ERR_CANT_WRITE (7) og 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);
}
Eksemplet viser kun mekanikken. Kontrol af filtypen, placering uden for webroot og spærring af scriptudførelse beskrives i guiden Implementér filuploads i PHP sikkert.
Uploads og PHP2Shell: Hvad de midlertidige foranstaltninger betyder
PHP2Shell-sårbarhederne CVE-2026-109618 og CVE-2026-109619 angår behandlingen omkring forespørgslen i PHP-fortolkeren. Derfor nævner php.net som midlertidige foranstaltninger blandt andet post_max_size = 8M (uden uploads) eller 32M (med uploads), upload_max_filesize derunder, samt session.upload_progress.enabled=0. Disse værdier hører hjemme i php.ini, FPM-pool eller serverkonfiguration, derefter skal alle workers genstarte.
Fagforfattere foreslår desuden at blokere multipart/form-data-forespørgsler uden for ægte uploadstier ved proxyen eller i WAF'en. Det er en uofficiel foranstaltning med risiko for, at formularer holder op med at virke; afvejningen beskriver indlægget Filtrér uploadforespørgsler i kanten. Ingen af disse foranstaltninger erstatter opdateringen til PHP 8.5.12, 8.4.27, 8.3.36 eller 8.2.35.
Hvordan hjælper PHP SOS konkret?
- Vi finder ud af, hvilke af jeres websites der reelt har brug for uploads, og på hvilke stier.
- Vi indstiller grænseværdierne og de midlertidige foranstaltninger pr. pool, genstarter alle workers og tester formularerne.
- Vi kontrollerer upload-mapper for eksekverbare filer og spærrer scriptudførelse som led i serverhærdningen.
- Vi indspiller PHP-opdateringen og dokumenterer patch-status pr. website.
Ofte stillede spørgsmål
Hvor gemmer PHP uploadede filer først?
I en midlertidig mappe. Som standard er det systemets midlertidige mappe; med upload_tmp_dir kan den ændres. Flyttes filen ikke i scriptet, sletter PHP den ved afslutningen af forespørgslen.
Kan jeg stole på feltet type i $_FILES?
Nej. MIME-typen oplyses af browseren, og ifølge manualen kontrollerer PHP den ikke. Også name og full_path stammer fra afsenderen. Den faktiske filtype afgør I på serversiden ud fra indholdet.
Hvorfor er $_FILES tom, selv om en fil blev sendt?
Oftest var hele forespørgslen større end post_max_size. Så tømmer PHP $_POST og $_FILES fuldstændigt, uden en fejlkode i arrayet. Andre årsager er et manglende enctype i formularen eller slåede file_uploads fra.
Hjælper det mod PHP2Shell at forbyde uploads i applikationen?
Nej. Behandlingen af forespørgselsdata sker i PHP, før applikationskoden kører. Effektive er PHP-opdateringen og genstart af alle workers; snævrere grænseværdier er kun en midlertidig foranstaltning.
Hvad er forskellen på upload_max_filesize og post_max_size?
upload_max_filesize begrænser hver enkelt fil, post_max_size hele forespørgslens indhold inklusive alle filer og felter. post_max_size skal derfor være større end upload_max_filesize.