Kort sagt: När en webbläsare laddar upp en fil skickar den en POST-förfrågan i formatet multipart/form-data, där formulärfält och filinnehåll står som enskilda delar efter varandra. PHP delar själv upp förfrågan, skriver varje fildel till en temporär fil i upload_tmp_dir, för in uppgifterna i $_FILES och startar först därefter ert skript. Skriptet flyttar filen med move_uploaded_file(); allt som inte flyttas raderar PHP i slutet av förfrågan.
En fils väg från webbläsaren till skriptet
För beslutsfattare är en sak viktigast: En uppladdning är inte något som bara applikationen sköter. En stor del av arbetet sker i PHP-tolken innan koden i CMS eller butik alls är inblandad. Därför påverkar fel i den här delen av PHP varje webbplats, oavsett om den har ett uppladdningsformulär eller inte.
Förloppet i korthet:
- Formuläret med
method="POST"ochenctype="multipart/form-data"skickas. - Webbservern tar emot förfrågan och för den vidare till PHP, beroende på driftläge via PHP-FPM, mod_php eller ett annat gränssnitt.
- PHP kontrollerar gränsvärden som
post_max_sizeoch delar upp innehållet med hjälp av en avgränsande teckenföljd, den så kallade boundary. - Vanliga fält hamnar i
$_POST, fildelar skrivs till temporära filer och beskrivs i$_FILES. - Först nu startar skriptet, kontrollerar filen och flyttar den till målplatsen.
- I slutet av förfrågan raderar PHP alla temporära filer som inte har flyttats.
multipart/form-data: uppbyggnaden av en uppladdningsförfrågan
En uppladdningsförfrågan är ren text med inbäddade fildata. Headern Content-Type anger boundary, och i innehållet skiljer den teckenföljden de enskilda delarna åt. Varje del har egna rubrikrader med fältnamn och eventuellt filnamn. Ett kraftigt förkortat, ofarligt exempel:
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--
Alla uppgifter i den här texten kommer från avsändaren, även filnamnet och delens Content-Type. PHP för över dem till $_FILES men kontrollerar inte om de är rimliga.
Arrayen $_FILES
För ett fält med namnet lebenslauf innehåller $_FILES['lebenslauf'] enligt PHP-manualen dessa nycklar:
| Nyckel | Innehåll | Pålitlig? |
|---|---|---|
name |
ursprungligt filnamn hos avsändaren | nej |
full_path |
sökväg som webbläsaren rapporterat, sedan PHP 8.1.0 | nej |
type |
MIME-typ enligt webbläsaren, kontrolleras inte av PHP | nej |
size |
storlek i byte | fastställs av PHP |
tmp_name |
sökväg till den temporära filen på servern | tilldelas av PHP |
error |
felkod från uppladdningsbearbetningen | sätts av PHP |
För fält med hakparenteser i namnet, till exempel bilder[], innehåller varje nyckel en array med en post per fil. Applikationen går då till exempel igenom $_FILES['bilder']['error'] och når tmp_name och name via samma index.
Ett formulär kan dessutom skicka ett dolt fält MAX_FILE_SIZE före filfältet. Enligt manualen är det bara ett tips till webbläsaren som lätt kringgås. Ni kan uteslutande förlita er på de gränser som servern genomdriver: i PHP, i webbservern och i själva applikationen.
Gränsvärden: php.ini-direktiven för uppladdningar
De här direktiven styr bearbetningen av uppladdningar. Standardvärdena kommer från PHP-manualen och gäller när ingen php.ini åsidosätter dem.
| Direktiv | Standard | Ändringsbar i |
|---|---|---|
file_uploads |
1 |
bara systemkonfigurationen |
upload_tmp_dir |
tom (system-temp) | bara systemkonfigurationen |
upload_max_filesize |
2M |
php.ini, pool, katalogkonfiguration |
post_max_size |
8M |
php.ini, pool, katalogkonfiguration |
max_file_uploads |
20 |
php.ini, pool, katalogkonfiguration |
enable_post_data_reading |
1 |
php.ini, pool, katalogkonfiguration |
post_max_size måste vara större än upload_max_filesize, och memory_limit bör enligt manualen i regel vara större än post_max_size. Går det inte att skriva till upload_tmp_dir väljer PHP i stället systemets temp-katalog. Med open_basedir måste temp-katalogen vara tillåten, annars misslyckas uppladdningarna. Praktiska värden och deras följder beskrivs i inlägget Begränsa post_max_size och upload_max_filesize rimligt.
Tolka felkoder rätt
Fältet error säger vad som hände under bearbetningen. Konstanterna går från UPLOAD_ERR_OK (0) via UPLOAD_ERR_INI_SIZE (1), UPLOAD_ERR_FORM_SIZE (2), UPLOAD_ERR_PARTIAL (3) och UPLOAD_ERR_NO_FILE (4) till UPLOAD_ERR_NO_TMP_DIR (6), UPLOAD_ERR_CANT_WRITE (7) och 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);
}
Exemplet visar bara mekaniken. Kontroll av filtyp, lagring utanför webbroten och spärr av skriptkörning beskrivs i guiden Implementera filuppladdningar i PHP säkert.
Uppladdningar och PHP2Shell: vad de tillfälliga åtgärderna innebär
PHP2Shell-sårbarheterna CVE-2026-109618 och CVE-2026-109619 gäller bearbetningen kring förfrågan i PHP-tolken. Därför nämner php.net som tillfälliga åtgärder bland annat post_max_size = 8M (utan uppladdningar) eller 32M (med uppladdningar), upload_max_filesize lägre än så, samt session.upload_progress.enabled=0. De här värdena hör hemma i php.ini, FPM-poolen eller serverkonfigurationen, därefter måste alla workers startas om.
Fackskribenter föreslår dessutom att multipart/form-data-förfrågningar utanför riktiga uppladdningssökvägar blockeras i proxyn eller WAF:en. Det är en inofficiell åtgärd med risk att formulär slutar fungera; avvägningen beskrivs i inlägget Filtrera uppladdningsförfrågningar i kanten. Ingen av de här åtgärderna ersätter uppdateringen till PHP 8.5.12, 8.4.27, 8.3.36 eller 8.2.35.
Hur hjälper PHP SOS konkret?
- Vi tar reda på vilka av era webbplatser som faktiskt behöver uppladdningar och på vilka sökvägar.
- Vi sätter gränsvärdena och de tillfälliga åtgärderna per pool, startar om alla workers och testar formulären.
- Vi kontrollerar uppladdningskataloger efter körbara filer och spärrar skriptkörning inom ramen för serverhärdning.
- Vi installerar PHP-uppdateringen och dokumenterar patchläget per webbplats.
Vanliga frågor
Var sparar PHP uppladdade filer först?
I en temporär katalog. Som standard är det systemets temporära katalog; med upload_tmp_dir kan den ändras. Flyttas filen inte i skriptet raderar PHP den i slutet av förfrågan.
Kan jag lita på fältet type i $_FILES?
Nej. MIME-typen rapporteras av webbläsaren, och PHP kontrollerar den enligt manualen inte. Även name och full_path kommer från avsändaren. Den faktiska filtypen fastställer ni på serversidan utifrån innehållet.
Varför är $_FILES tom fast en fil skickades?
Oftast var hela förfrågan större än post_max_size. Då tömmer PHP $_POST och $_FILES helt, utan felkod i arrayen. Andra orsaker är ett saknat enctype i formuläret eller avstängda file_uploads.
Hjälper det mot PHP2Shell att förbjuda uppladdningar i applikationen?
Nej. Bearbetningen av förfrågningsdata sker i PHP innan applikationskoden körs. Det som fungerar är PHP-uppdateringen och omstart av alla workers; snävare gränsvärden är bara en tillfällig åtgärd.
Vad är skillnaden mellan upload_max_filesize och post_max_size?
upload_max_filesize begränsar varje enskild fil, post_max_size hela förfrågans innehåll inklusive alla filer och fält. post_max_size måste därför vara större än upload_max_filesize.