Tra la pubblicazione di una vulnerabilità di PHP e l’installazione del pacchetto corretto passa sempre del tempo. Su un ramo supportato sono di solito pochi giorni, il tempo che la distribuzione o il repository pubblichino l’aggiornamento. Su un ramo fuori supporto il tempo dipende da quando arriva un backport o da quando si completa la migrazione. In quella finestra il server resta esposto, a meno di ridurre la superficie di attacco con le mitigazioni.
In questo articolo vediamo quali mitigazioni sono davvero utili per una vulnerabilità di PHP, organizzate per livello: estensioni, SAPI e server web, configurazione dell’interprete, filtri e accessi davanti all’applicazione. Per ognuna diciamo cosa blocca, cosa lascia aperto e come verificare che funzioni. Una premessa vale per tutto l’articolo: una mitigazione riduce il rischio in attesa della correzione e non la sostituisce.
Prima di tutto: dove si trova la falla
La mitigazione giusta dipende dalla parte di PHP in cui si trova il difetto. Gli avvisi di sicurezza e le schede su NVD lo indicano quasi sempre, e conviene leggerli con questa domanda in mente. Le vulnerabilità di PHP ricadono in poche famiglie:
- in un’estensione, come la CVE-2023-3824 nella lettura degli archivi phar o la CVE-2022-31626 nel driver
pdo_mysqlcon mysqlnd; - in una SAPI, come la CVE-2019-11043 di PHP-FPM con nginx o la CVE-2024-4577 di PHP-CGI su Windows;
- nel nucleo dell’interprete, per esempio nella gestione delle richieste multipart, dei cookie o delle stringhe;
- in una libreria esterna usata da PHP, come OpenSSL o libxml2, che segue gli aggiornamenti del sistema operativo.
Una falla in un’estensione si mitiga bene, quella nel nucleo molto meno. Lo schema qui sotto riassume quale leva usare in ciascun caso.
Estensioni: disattivare quello che non serve
È la mitigazione più efficace, perché toglie del tutto il codice vulnerabile dal processo. Molte installazioni caricano decine di estensioni installate anni fa e mai usate. Se la vulnerabilità riguarda una di queste, disattivarla chiude il problema fino alla correzione e spesso anche dopo.
# Estensioni caricate (CLI e PHP-FPM possono differire)
php -m
php-fpm -m 2>/dev/null
# Famiglia Red Hat: trova il file .ini che carica l'estensione
grep -l 'extension *= *phar' /etc/php.d/*.ini /etc/opt/remi/*/php.d/*.ini 2>/dev/null
# Debian e Ubuntu: disattiva il modulo per la SAPI fpm della versione 7.4
phpdismod -v 7.4 -s fpm phar
systemctl restart php7.4-fpm
Sulla famiglia Red Hat si commenta la riga extension= nel file trovato e si riavvia PHP-FPM. Prima di farlo bisogna cercare nel codice e nelle dipendenze se l’estensione viene usata: phar, per esempio, serve a Composer e ad alcune librerie che si distribuiscono come archivio. Se un’estensione è compilata dentro l’eseguibile anziché caricata come modulo, compare in php -m ma non ha un file .ini: in quel caso non si disattiva senza ricompilare.
Alcune falle si mitigano anche senza disattivare l’estensione. La CVE-2022-31626, per esempio, richiede che un utente possa indicare l’host e la password di una connessione al database: un’applicazione che usa solo credenziali fisse in configurazione non offre quella condizione, mentre uno strumento di installazione o di amministrazione dimenticato online sì. Rimuovere quegli script è una mitigazione a costo zero.
SAPI e server web
Molte vulnerabilità gravi dipendono dal modo in cui le richieste arrivano a PHP. In questi casi la configurazione del server web è la prima linea di difesa. Per la CVE-2019-11043 bastava che nginx verificasse l’esistenza dello script prima di passare la richiesta a PHP-FPM, oppure che non inviasse un PATH_INFO vuoto: ne parliamo nel dettaglio nell’articolo dedicato alla CVE-2019-11043.
Anche PHP-FPM offre alcune direttive utili, documentate nella pagina di configurazione di FPM su php.net:
security.limit_extensionslimita le estensioni dei file che FPM accetta di eseguire; il valore predefinito è.php .phar, e restringerlo a.phpevita che un errore di configurazione del server web faccia eseguire altri tipi di file;clear_env, attivo per impostazione predefinita, impedisce che variabili d’ambiente arbitrarie arrivino ai processi;useregroupper pool permettono di eseguire ogni applicazione con un utente diverso, così una compromissione resta confinata ai file di quel sito.
Quando la falla è in una SAPI che non è indispensabile, la mitigazione più netta è smettere di usarla. Un PHP-CGI raggiungibile direttamente dal web, per esempio, è da sostituire con PHP-FPM. Le differenze di esposizione tra le SAPI sono il tema dell’articolo su PHP-FPM, mod_php e CGI.
Configurazione di PHP
Alcune direttive del php.ini limitano cosa può fare un attaccante che riesce a sfruttare una falla o a caricare uno script. Sono utili, purché se ne conoscano i limiti dichiarati dalla documentazione stessa.
; php.ini o file in /etc/php.d/ (Red Hat) o conf.d/ (Debian e Ubuntu)
; Funzioni che eseguono comandi di sistema: da provare con l'applicazione,
; alcune librerie le usano. La documentazione avverte che si possono aggirare.
disable_functions = exec,passthru,shell_exec,system,proc_open,popen
; Nessuna inclusione di codice da URL remoti (è già il valore predefinito)
allow_url_include = Off
; Limiti sulle richieste: riducono la superficie dei difetti nel parsing
post_max_size = 16M
upload_max_filesize = 8M
max_input_vars = 1000
; Non annuncia la versione nell'intestazione X-Powered-By
expose_php = Off
Due avvertenze. La documentazione delle direttive dice esplicitamente che disable_functions può essere aggirata e non va considerata una misura di sicurezza sufficiente. E la politica di sicurezza del progetto PHP non classifica come vulnerabilità un aggiramento di open_basedir: va considerato un confine utile contro gli errori di configurazione, inadatto a fermare un attaccante determinato. Quanto a expose_php = Off, nasconde la versione ma non corregge nulla; serve solo a non offrire l’informazione già pronta.
Davanti all’applicazione: filtri e accessi
L’ultimo livello agisce prima che la richiesta arrivi a PHP. Un firewall applicativo può bloccare le richieste che corrispondono alla firma di un exploit noto, ed è utile soprattutto quando l’attacco ha una forma riconoscibile, come un carattere particolare nel percorso o un’intestazione anomala. Il limite è che una firma blocca le varianti note: chi conosce la vulnerabilità può costruire una richiesta diversa con lo stesso effetto.
Più robusta è la restrizione degli accessi. Pannelli di amministrazione, strumenti di importazione, endpoint di servizio e vecchi script di installazione possono stare dietro una VPN, un elenco di indirizzi IP autorizzati o un’autenticazione del server web. Se la vulnerabilità richiede di raggiungere una di quelle funzioni, la restrizione la rende inaccessibile a chi sta fuori.
C’è poi l’isolamento del server nel suo complesso. Un server web che non ha bisogno di aprire connessioni verso Internet può avere il traffico in uscita bloccato dal firewall, a eccezione dei servizi necessari come il database, i repository dei pacchetti o le API dei fornitori. Se un attaccante riesce a eseguire codice, questa regola gli rende molto più difficile scaricare altri strumenti o inviare dati all’esterno. È una misura che non dipende dalla singola CVE e conviene mantenere anche dopo la correzione.
Verificare che la mitigazione funzioni
Una mitigazione non verificata è un’ipotesi. Per ognuna serve una prova concreta, ripetibile anche da chi non l’ha applicata:
- per un’estensione disattivata, controllare che non compaia più nell’elenco dei moduli della SAPI usata dal sito, che può differire da quello della riga di comando;
- per una regola del server web, inviare una richiesta che dovrebbe essere bloccata e verificare la risposta, per esempio un 404 o un 403;
- per una direttiva di PHP, leggere il valore effettivo con una pagina temporanea che stampi
ini_get(), da cancellare subito dopo; - in ogni caso, provare i percorsi principali dell’applicazione e controllare i log per errori nuovi.
Ogni mitigazione va anche annotata, con data, motivo e CVE di riferimento. Dopo la correzione alcune vanno tolte, perché limitano funzioni utili; altre conviene tenerle, come la disattivazione delle estensioni inutili o la restrizione degli accessi amministrativi.
Quanto può durare una mitigazione
Il tempo che serve per arrivare alla correzione. Su un ramo supportato è l’attesa dell’aggiornamento. Su un ramo fuori supporto l’aggiornamento non arriva, e la mitigazione rischia di diventare permanente senza che nessuno l’abbia deciso. La strada per chiudere davvero la vulnerabilità è il backport della patch sul ramo in uso, che per un intervento d’urgenza su una singola CVE ha un obiettivo di 24 ore; la sequenza completa è descritta nell’articolo sulle prime 24 ore dopo una CVE critica.
Immaginiamo un portale su PHP 7.4 in cui, dopo una CVE in un’estensione, l’estensione è stata disattivata e tutto ha continuato a funzionare. È facile considerare chiusa la questione. Due anni dopo un nuovo plugin richiede proprio quell’estensione, qualcuno la riattiva per far funzionare il sito e la vulnerabilità torna aperta senza che nessuno se ne accorga. Una mitigazione senza scadenza e senza documentazione produce esattamente questo tipo di situazione.
Conclusioni
Mitigare una vulnerabilità di PHP prima della patch significa ridurre la superficie di attacco nella finestra che separa la pubblicazione della CVE dalla correzione. La scelta dipende da dove si trova la falla. Se è in un’estensione non usata, disattivarla è la misura più efficace. Se dipende dalla SAPI o dal server web, la configurazione di nginx, di Apache o dei pool di PHP-FPM può interrompere le condizioni dell’attacco. Le direttive del php.ini e i filtri davanti all’applicazione limitano i danni, con i limiti che la documentazione stessa dichiara. Ogni mitigazione va verificata, annotata e accompagnata da una data per la correzione: un aggiornamento sui rami supportati, un backport su quelli fuori supporto.