Nell’ottobre 2019 una vulnerabilità di PHP-FPM ha fatto il giro dei bollettini di sicurezza: con una configurazione di nginx molto diffusa, bastava una richiesta HTTP costruita ad arte per eseguire codice sul server, senza autenticazione. Le correzioni sono uscite in poche settimane, insieme a un exploit pubblico. Sette anni dopo la CVE-2019-11043 è ancora nel catalogo delle vulnerabilità sfruttate attivamente dell’agenzia statunitense CISA, e continua a riguardare i server su cui PHP non è mai stato aggiornato.
In questo articolo ricostruiamo cosa è successo, come funziona la falla, quali versioni e quali configurazioni sono vulnerabili e perché proprio i server con PHP fuori supporto restano scoperti. Chiudiamo con i comandi per verificare la propria configurazione di nginx e con le correzioni e le mitigazioni documentate.
Cosa è successo nel 2019
Il problema è stato segnalato al progetto PHP il 26 settembre 2019 con il bug #78599, intitolato «env_path_info underflow in fpm_main.c can lead to RCE». Il difetto è stato scoperto dai ricercatori Emil Lerner e Andrew Danau, che hanno poi pubblicato un exploit funzionante. Il 24 ottobre 2019 il progetto PHP ha pubblicato le versioni correttive 7.1.33, 7.2.24 e 7.3.11; la 7.4.0, uscita a novembre, conteneva già la correzione.
La scheda NVD assegna alla vulnerabilità un punteggio CVSS 3.1 di 9.8, critico. Il punteggio indicato da PHP, l’ente che ha assegnato la CVE, è più basso, 8.7, perché considera alta la complessità dell’attacco. La differenza conta poco nella pratica: l’exploit era pubblico e automatizzato, e il 25 marzo 2022 la CISA ha inserito la CVE nel suo catalogo delle vulnerabilità note e sfruttate.
Come funziona la vulnerabilità
Il ruolo di nginx
nginx non esegue PHP: inoltra le richieste a PHP-FPM con il protocollo FastCGI, passando una serie di parametri che descrivono la richiesta. Due di questi sono SCRIPT_FILENAME, il file da eseguire, e PATH_INFO, la parte dell’indirizzo che segue il nome dello script, come /articolo/42 in /index.php/articolo/42. Per separarli si usa la direttiva fastcgi_split_path_info con un’espressione regolare, come mostra la documentazione di nginx.
Un’espressione molto diffusa nelle configurazioni di esempio era ^(.+?\.php)(/.*)$. Il punto, nelle espressioni regolari, non corrisponde al carattere di nuova riga. Inserendo un %0a codificato nel percorso, l’espressione non trova corrispondenza e PATH_INFO arriva a PHP-FPM vuoto.
Cosa succede in PHP-FPM
Nel file sapi/fpm/fpm/fpm_main.c, la funzione che ricostruisce i percorsi della richiesta calcolava la posizione di PATH_INFO sottraendo una lunghezza a un puntatore, senza verificare che il risultato restasse dentro il buffer. Con un PATH_INFO vuoto il puntatore finiva prima dell’inizio dell’area di memoria e PHP-FPM scriveva in una zona riservata ai dati del protocollo FastCGI. L’exploit pubblico sfrutta questa scrittura per alterare i parametri della richiesta e iniettare direttive di configurazione di PHP, fino a eseguire comandi sul server con i privilegi del pool.
La correzione pubblicata dal progetto PHP aggiunge proprio il controllo mancante: il puntatore viene calcolato solo se la lunghezza di PATH_INFO è maggiore di quella da sottrarre. Sono due righe di codice, accompagnate da un test automatico che riproduce il caso.
Chi è vulnerabile
La vulnerabilità è nel codice di PHP-FPM, ma per sfruttarla dall’esterno servono più condizioni insieme. Secondo la descrizione pubblicata dagli autori dell’exploit, citata da NVD, sono queste:
- nginx inoltra a PHP-FPM le richieste che corrispondono a un blocco come
location ~ [^/]\.php(/|$); - il blocco usa
fastcgi_split_path_infocon un’espressione che inizia con^e finisce con$, come^(.+?\.php)(/.*)$; PATH_INFOviene passato confastcgi_param PATH_INFO $fastcgi_path_info;;- manca un controllo sull’esistenza dello script, come
try_files $uri =404;, per cui le richieste verso file inesistenti arrivano comunque a PHP-FPM; - la versione di PHP-FPM non contiene la correzione.
L’analisi di Red Hat precisa che il codice difettoso viene usato solo quando PHP-FPM riceve le richieste da nginx. Chi esegue PHP come modulo di Apache, con mod_php, non è esposto a questa falla; della differenza tra le SAPI parliamo nell’articolo su PHP-FPM, mod_php e CGI.
Versioni affette e correzioni
NVD elenca come vulnerabili i rami allora supportati, prima delle versioni correttive. Il difetto però è più vecchio: gli autori dell’exploit segnalano che il bug esiste anche in PHP 5, mentre il loro exploit funziona su PHP 7. Lo confermano le correzioni pubblicate dalle distribuzioni per rami che il progetto PHP non aggiornava più.
| Ramo | Stato a ottobre 2019 | Correzione |
|---|---|---|
| PHP 5.x | fuori supporto | nessun rilascio ufficiale; backport di alcune distribuzioni (Red Hat su 5.3 e 5.4, Ubuntu ESM su 5.3 e 5.5) |
| PHP 7.0 | fuori supporto da gennaio 2019 | nessun rilascio ufficiale; backport di Debian 9 e Ubuntu 16.04 |
| PHP 7.1 | solo sicurezza | 7.1.33, 24 ottobre 2019 |
| PHP 7.2 | supporto attivo | 7.2.24, 24 ottobre 2019 |
| PHP 7.3 | supporto attivo | 7.3.11, 24 ottobre 2019 |
| PHP 7.4 | non ancora rilasciato | correzione inclusa in 7.4.0 |
Perché i server legacy restano esposti
Sui rami supportati la CVE-2019-11043 è stata chiusa con un aggiornamento di routine. Sui server legacy la situazione è diversa, per diverse ragioni che spesso si sommano:
- nessun rilascio ufficiale: chi usa PHP 5.6 o 7.0 compilato a mano, o installato da un repository che non ha pubblicato la correzione, ha ancora il codice vulnerabile;
- configurazioni copiate: il blocco
locationvulnerabile veniva da guide e configurazioni di esempio molto diffuse, ed è rimasto identico per anni su server che nessuno ha più toccato; - sistemi operativi fuori supporto: su una distribuzione senza più aggiornamenti, anche un PHP che all’epoca era stato corretto non riceve le CVE successive;
- exploit automatizzato: lo strumento pubblico verifica da solo se un indirizzo è vulnerabile, quindi non serve un attaccante esperto per trovare i server scoperti.
Il risultato è che una vulnerabilità del 2019 resta un rischio concreto nel 2026 proprio sulle macchine che hanno meno manutenzione.
Come verificare la tua configurazione
Il controllo richiede pochi minuti. Servono la configurazione completa di nginx, la versione di PHP-FPM e la provenienza del pacchetto:
# Configurazione completa di nginx, con i file inclusi:
# cerca split_path_info, PATH_INFO e i controlli di esistenza
nginx -T 2>/dev/null | grep -nE 'fastcgi_split_path_info|PATH_INFO|try_files|-f \$'
# Versione di PHP-FPM effettivamente in esecuzione
ps -eo args | grep -E 'php-fpm: master' | grep -v grep
php-fpm -v 2>/dev/null || ls /usr/sbin/php-fpm*
# Il pacchetto contiene la correzione? Cerca la CVE nel changelog
rpm -q --changelog php-fpm | grep -i 2019-11043 # RHEL e derivate
zcat /usr/share/doc/php*-fpm/changelog.Debian.gz | grep -i 2019-11043 # Debian e Ubuntu
Se la configurazione contiene fastcgi_split_path_info con un’espressione da ^ a $, passa PATH_INFO senza condizioni e non verifica l’esistenza dello script, e la versione di PHP-FPM è precedente alle correzioni senza un backport documentato nel changelog, il server va considerato esposto. Su un PHP compilato a mano il changelog non c’è: in quel caso fa fede solo la verifica del codice sorgente usato per la compilazione.
Correzioni e mitigazioni documentate
La soluzione è installare un PHP-FPM che contenga la correzione: una versione supportata, il pacchetto aggiornato della distribuzione oppure un pacchetto con il backport della patch sul ramo in uso. In attesa del pacchetto, o come difesa in più, si può interrompere una delle condizioni dal lato di nginx. Red Hat documenta la modifica di PATH_INFO con il parametro if_not_empty, mentre gli autori dell’exploit indicano il controllo di esistenza dello script come il modo più semplice per bloccare l’attacco:
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
# Se lo script non esiste, risponde 404 senza interpellare PHP-FPM
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# PATH_INFO viene passato solo se non è vuoto (mitigazione di Red Hat)
fastcgi_param PATH_INFO $fastcgi_path_info if_not_empty;
fastcgi_pass unix:/run/php-fpm/www.sock;
}
Dopo la modifica si verifica la sintassi con nginx -t e si ricarica il servizio. Il percorso del socket in fastcgi_pass cambia da sistema a sistema e va lasciato quello già in uso. Se l’applicazione non usa PATH_INFO, la scelta più semplice è non passarlo affatto e usare try_files $uri =404;. Queste modifiche bloccano l’exploit pubblico ma lasciano il difetto nel codice: le abbiamo approfondite, insieme alle altre misure possibili, nell’articolo su come mitigare una vulnerabilità prima della patch.
Cosa insegna questa CVE
La CVE-2019-11043 è un buon esempio di come l’esposizione dipenda dalla combinazione tra versione e configurazione. Lo stesso PHP-FPM poteva essere innocuo dietro una configurazione di nginx e sfruttabile dietro un’altra, e lo stesso numero di versione poteva essere corretto o vulnerabile a seconda di chi aveva costruito il pacchetto. Per questo un’analisi seria parte sempre dall’inventario di versione, SAPI, configurazione del server web e provenienza dei pacchetti.
Insegna anche che le vulnerabilità non scadono. Un exploit pubblico continua a funzionare finché esistono server che non sono stati corretti, e i server con PHP fuori supporto sono esattamente quelli dove le correzioni non arrivano da sole. Se hai dubbi su un server di produzione, la nostra reperibilità per le urgenze è attiva H24.
Per concludere
La CVE-2019-11043 è un difetto di PHP-FPM che, con nginx configurato con una certa espressione in fastcgi_split_path_info, il passaggio di PATH_INFO e nessun controllo sull’esistenza dello script, permette l’esecuzione di codice da remoto. Il progetto PHP l’ha corretta il 24 ottobre 2019 nelle versioni 7.1.33, 7.2.24 e 7.3.11; i rami già fuori supporto, dal 5.x al 7.0, non hanno avuto rilasci ufficiali e sono stati corretti solo dalle distribuzioni che li mantenevano. Con un exploit pubblico e la presenza nel catalogo CISA delle vulnerabilità sfruttate, resta un rischio reale sui server legacy. Verificare la configurazione di nginx e il changelog del pacchetto richiede pochi minuti; le modifiche a nginx bloccano l’exploit, mentre la correzione del codice, con un aggiornamento o un backport, chiude il problema.