Situazione attuale: PHP2Shell. Due vulnerabilità critiche di PHP (CVSS 9,2) consentono codice dannoso senza autenticazione. Patch dal 21/10/2026.Che cosa fare ora →

Come PHP elabora gli upload di file

Tra il clic su «Carica» e il Suo codice c'è un'intera catena di elaborazione in PHP. Questa pagina spiega multipart/form-data, file temporanei, $_FILES, limiti e il legame con PHP2Shell.

Stato 5 min di letturaConoscere PHP

In breve: quando un browser carica un file, invia una richiesta POST in formato multipart/form-data, in cui i campi del modulo e il contenuto dei file si susseguono come parti distinte. PHP scompone da sé questa richiesta, scrive ogni parte-file in un file temporaneo in upload_tmp_dir, registra i dati in $_FILES e solo allora avvia il Suo script. Lo script sposta il file con move_uploaded_file(); tutto ciò che non viene spostato, PHP lo elimina al termine della richiesta.

Il percorso di un file dal browser allo script

Per chi decide, una cosa conta più di tutte: un upload non è un'operazione della sola applicazione. Gran parte del lavoro avviene nell'interprete PHP, prima ancora che il codice del CMS o dello shop entri in gioco. Per questo gli errori in questa parte di PHP riguardano ogni sito web, che abbia o no un modulo di upload.

Lo svolgimento in sintesi:

  1. Il modulo con method="POST" e enctype="multipart/form-data" viene inviato.
  2. Il server web accetta la richiesta e la passa a PHP, a seconda della modalità di esecuzione tramite PHP-FPM, mod_php o un'altra interfaccia.
  3. PHP controlla limiti come post_max_size e scompone il contenuto in base a una sequenza di separazione, il cosiddetto boundary.
  4. I campi normali finiscono in $_POST, le parti-file vengono scritte in file temporanei e descritte in $_FILES.
  5. Solo adesso parte lo script, che controlla il file e lo sposta nella destinazione.
  6. Al termine della richiesta PHP elimina tutti i file temporanei che non sono stati spostati.

multipart/form-data: la struttura di una richiesta di upload

Una richiesta di upload è puro testo con dati di file incorporati. L'header Content-Type indica il boundary, e nel contenuto questa sequenza di caratteri separa le singole parti. Ogni parte ha intestazioni proprie con il nome del campo e, se presente, il nome del file. Un esempio molto abbreviato e innocuo:

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--

Tutti i dati di questo testo provengono dal mittente, compresi il nome del file e il Content-Type della parte. PHP li riporta in $_FILES, ma non ne verifica la plausibilità.

L'array $_FILES

Per un campo di nome lebenslauf, secondo il manuale PHP $_FILES['lebenslauf'] contiene queste chiavi:

Chiave Contenuto Affidabile?
name nome originale del file presso il mittente no
full_path percorso comunicato dal browser, da PHP 8.1.0 no
type tipo MIME secondo il browser, non verificato da PHP no
size dimensione in byte determinata da PHP
tmp_name percorso del file temporaneo sul server assegnato da PHP
error codice di errore dell'elaborazione dell'upload impostato da PHP

Nei campi con parentesi quadre nel nome, ad esempio bilder[], ogni chiave contiene un array con una voce per file. L'applicazione scorre allora, ad esempio, $_FILES['bilder']['error'] e accede con lo stesso indice a tmp_name e name.

Un modulo può inoltre inviare, prima del campo file, un campo nascosto MAX_FILE_SIZE. Secondo il manuale è solo un suggerimento per il browser, facile da aggirare. Può fare affidamento soltanto sui limiti che il server fa rispettare: in PHP, nel server web e nell'applicazione stessa.

Limiti: le direttive di php.ini per gli upload

Queste direttive governano l'elaborazione degli upload. I valori predefiniti provengono dal manuale PHP e valgono quando nessun php.ini li sovrascrive.

Direttiva Predefinito Modificabile in
file_uploads 1 solo configurazione di sistema
upload_tmp_dir vuoto (temp di sistema) solo configurazione di sistema
upload_max_filesize 2M php.ini, pool, configurazione per directory
post_max_size 8M php.ini, pool, configurazione per directory
max_file_uploads 20 php.ini, pool, configurazione per directory
enable_post_data_reading 1 php.ini, pool, configurazione per directory

post_max_size deve essere maggiore di upload_max_filesize, e secondo il manuale memory_limit dovrebbe di norma essere maggiore di post_max_size. Se upload_tmp_dir non è scrivibile, PHP ripiega sulla directory temp del sistema. Con open_basedir la directory temp deve essere consentita, altrimenti gli upload falliscono. I valori pratici e le loro conseguenze sono descritti nell'articolo Limitare in modo sensato post_max_size e upload_max_filesize.

Valutare correttamente i codici di errore

Il campo error indica che cosa è successo durante l'elaborazione. Le costanti vanno da UPLOAD_ERR_OK (0), passando per UPLOAD_ERR_INI_SIZE (1), UPLOAD_ERR_FORM_SIZE (2), UPLOAD_ERR_PARTIAL (3) e UPLOAD_ERR_NO_FILE (4), fino a UPLOAD_ERR_NO_TMP_DIR (6), UPLOAD_ERR_CANT_WRITE (7) e 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);
}

L'esempio mostra solo il meccanismo. Il controllo del tipo di file, la collocazione fuori dal webroot e il blocco dell'esecuzione degli script sono descritti nella guida Implementare in modo sicuro gli upload di file in PHP.

Upload e PHP2Shell: che cosa significano le misure transitorie

Le vulnerabilità PHP2Shell CVE-2026-109618 e CVE-2026-109619 riguardano l'elaborazione che avviene attorno alla richiesta nell'interprete PHP. Per questo php.net indica come misure transitorie, tra l'altro, post_max_size = 8M (senza upload) oppure 32M (con upload), upload_max_filesize al di sotto di questo valore, oltre a session.upload_progress.enabled=0. Questi valori vanno inseriti in php.ini, nel pool FPM o nella configurazione del server; poi tutti i worker devono essere riavviati.

Alcuni autori di settore propongono inoltre di bloccare nel proxy o nel WAF le richieste multipart/form-data al di fuori dei veri percorsi di upload. È una misura non ufficiale, con il rischio di rompere dei moduli; la valutazione è descritta nell'articolo Filtrare le richieste di upload al perimetro. Nessuna di queste misure sostituisce l'aggiornamento a PHP 8.5.12, 8.4.27, 8.3.36 o 8.2.35.

Come aiuta concretamente PHP SOS?

  • Verifichiamo quali dei Suoi siti web hanno davvero bisogno di upload e su quali percorsi.
  • Impostiamo i limiti e le misure transitorie per ogni pool, riavviamo tutti i worker e testiamo i moduli.
  • Controlliamo che nelle directory di upload non ci siano file eseguibili e blocchiamo l'esecuzione degli script nell'ambito del rafforzamento del server.
  • Installiamo l'aggiornamento di PHP e documentiamo lo stato delle patch per ogni sito web.

Domande frequenti

Dove salva PHP per prima cosa i file caricati?

In una directory temporanea. Per impostazione predefinita è la directory temporanea del sistema; con upload_tmp_dir si può cambiare. Se lo script non sposta il file, PHP lo elimina al termine della richiesta.

Posso fidarmi del campo type in $_FILES?

No. Il tipo MIME lo comunica il browser, e secondo il manuale PHP non lo verifica. Anche name e full_path provengono dal mittente. Il tipo effettivo del file lo determina sul server in base al contenuto.

Perché $_FILES è vuoto anche se è stato inviato un file?

Di solito l'intera richiesta era più grande di post_max_size. In tal caso PHP svuota completamente $_POST e $_FILES, senza alcun codice di errore nell'array. Altre cause sono un enctype mancante nel modulo o file_uploads disattivato.

Vietare gli upload nell'applicazione aiuta contro PHP2Shell?

No. L'elaborazione dei dati della richiesta avviene in PHP, prima che giri il codice dell'applicazione. Sono efficaci l'aggiornamento di PHP e il riavvio di tutti i worker; limiti più stretti sono soltanto una misura transitoria.

Qual è la differenza tra upload_max_filesize e post_max_size?

upload_max_filesize limita ogni singolo file, post_max_size l'intero contenuto della richiesta, inclusi tutti i file e i campi. Per questo post_max_size deve essere maggiore di upload_max_filesize.

Fonti

Contatto immediato

Ogni ora conta. Ci scriva adesso.

Descriva brevemente sistema, versione di PHP e quanto osservato. Ci faremo sentire, chiariremo la situazione e lavoreremo da remoto fino alla chiusura e alla documentazione dell'incidente.

Aiuto immediato