CVE-2019-11043: la falla di PHP-FPM con nginx e perché i server legacy restano esposti

Come funziona la vulnerabilità di PHP-FPM che con alcune configurazioni di nginx permette l’esecuzione di codice da remoto, quali versioni e configurazioni sono esposte, come verificarle e perché i server con PHP fuori supporto restano scoperti.

Copertina con il logo php∞, la categoria Sicurezza e CVE, il titolo CVE-2019-11043: PHP-FPM e nginx e il numero 11

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.

Schema in cinque passaggi della CVE-2019-11043. 1, l’attaccante invia una richiesta a uno script PHP con un carattere di nuova riga codificato come %0a nel percorso. 2, in nginx l’espressione regolare di fastcgi_split_path_info, ^(.+?\.php)(/.*)$, non trova corrispondenza perché il punto non accetta la nuova riga, e PATH_INFO viene passato vuoto a PHP-FPM. 3, in fpm_main.c il calcolo della posizione di PATH_INFO va sotto l’inizio del buffer. 4, PHP-FPM scrive nella memoria che contiene i dati del protocollo FastCGI. 5, l’exploit pubblico usa la scrittura per iniettare direttive di configurazione di PHP ed eseguire codice sul server. In basso, la correzione ufficiale: un controllo sulla lunghezza aggiunto in due righe di fpm_main.c
Un carattere di nuova riga, un’espressione regolare che non lo prevede e un calcolo senza controllo dei limiti.

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_info con un’espressione che inizia con ^ e finisce con $, come ^(.+?\.php)(/.*)$;
  • PATH_INFO viene passato con fastcgi_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.

Tabella visiva delle condizioni per sfruttare la CVE-2019-11043 dall’esterno, ciascuna con la contromisura che la interrompe. nginx con PHP-FPM: con mod_php su Apache il codice vulnerabile non viene usato. fastcgi_split_path_info con espressione da ^ a $: modificare l’espressione non è una mitigazione documentata. PATH_INFO passato a PHP-FPM: con if_not_empty un valore vuoto non viene inviato. Nessun controllo di esistenza dello script: try_files o un controllo -f restituiscono 404 prima di PHP-FPM. PHP-FPM senza correzione: versioni ufficiali 7.1.33, 7.2.24, 7.3.11 e successive, oppure pacchetto con il backport. Il passaggio di PATH_INFO con if_not_empty e il controllo di esistenza dello script bloccano l’exploit pubblico, ma solo la correzione elimina il difetto
Due modifiche a nginx bloccano l’exploit pubblico; solo la correzione del codice chiude il difetto.

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

RamoStato a ottobre 2019Correzione
PHP 5.xfuori supportonessun rilascio ufficiale; backport di alcune distribuzioni (Red Hat su 5.3 e 5.4, Ubuntu ESM su 5.3 e 5.5)
PHP 7.0fuori supporto da gennaio 2019nessun rilascio ufficiale; backport di Debian 9 e Ubuntu 16.04
PHP 7.1solo sicurezza7.1.33, 24 ottobre 2019
PHP 7.2supporto attivo7.2.24, 24 ottobre 2019
PHP 7.3supporto attivo7.3.11, 24 ottobre 2019
PHP 7.4non ancora rilasciatocorrezione inclusa in 7.4.0
Fonti: NVD, ChangeLog di PHP 7, avvisi di sicurezza di Red Hat, Debian e Ubuntu.

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 location vulnerabile 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.

IL PROSSIMO PASSO

Mettiamo
in sicurezza.

Indicaci versione di PHP, sistema operativo e cosa ti preoccupa. Ti risponderemo con una valutazione dell’esposizione e una proposta di intervento.

Richiedi supporto

Ti porta al modulo di richiesta, in quattro passi.

PHP LEGACY · MANAGED SERVER S.R.L.info@phplegacy.it +39 02 5656 9681

Lunedì – venerdì · 09:30 – 19:30

Urgenza in corso? Reperibilità H24
+39 340 985 4556 ↗