Apri la scheda Rete del tuo browser su qualsiasi sito web basato su PHP e guarda gli header di risposta per il documento HTML principale. Su molte installazioni troverai un header simile a questo: X-Powered-By: PHP/8.1.27. Quello è PHP che dice utilmente a tutto Internet quale versione sta eseguendo. È un piccolo dettaglio che la maggior parte dei proprietari di siti non nota mai, ma risulta essere uno dei pezzi più facili di informazione che un aggressore raccoglie prima di decidere se un sito vale la pena di essere indagato ulteriormente.
Questa guida spiega perché l'header esiste, cosa espone effettivamente e ogni modo realistico per liberarsene. Dalla configurazione del server al livello di un singolo sito WordPress su shared hosting dove non puoi toccare il php.ini.
Cosa dice davvero l'header a un aggressore
Un valore come PHP/8.1.27 rivela due fatti utili. Primo, la versione principale. Se sei ancora su PHP 7.4 o PHP 8.0, entrambi raggiunto la fine della loro vita, un aggressore sa immediatamente che non arriveranno più patch di sicurezza. Secondo, il livello esatto della patch. Tra due release di patch ci sono solitamente una manciata di CVE pubblicamente noti. Uno scanner può confrontare la tua versione segnalata con il suo database CVE e decidere in millisecondi se continuare a sondare o passare oltre.
Nascondere la versione ovviamente non patcha nulla. Se il tuo PHP è obsoleto, è ancora obsoleto. Ma rimuovere la trasmissione fa tre cose concrete: ferma gli scanner di massa automatizzati dal contrassegnare il tuo sito come bersaglio economico, rende la ricognizione manuale leggermente più costosa e rimuove un pezzo di informazione che semplicemente non ha nulla a che fare nei tuoi header di risposta. Non c'è una ragione legittima per cui Internet pubblico debba conoscere il livello di patch del tuo PHP.
Alcuni framework di conformità e checklist di sicurezza (PCI-DSS, BSI IT-Grundschutz, audit interni in aziende più grandi) chiedono esplicitamente di sopprimere questo header. Anche se non lavori in uno di quei contesti, è una correzione di cinque minuti senza svantaggi.
Da dove viene l'header?
L'header X-Powered-By è emesso da PHP stesso, non dal tuo webserver. Il comportamento è controllato da una singola direttiva php.ini chiamata expose_php. Quando quella direttiva è impostata su On (che è il valore predefinito nella maggior parte delle distribuzioni PHP), PHP aggiunge automaticamente l'header a ogni risposta. Disattivalo e l'header scompare per ogni sito su quell'installazione PHP.
In aggiunta, alcuni framework o applicazioni aggiungono i propri valori X-Powered-By. Express, ASP.NET e altri lo fanno. WordPress stesso non lo fa, ma a volte i plugin sì. Quindi, dopo aver disattivato la divulgazione della versione di PHP, vale la pena controllare di nuovo gli header di risposta per vedere se qualcos'altro trapela.
Opzione 1: disattivare expose_php in php.ini
Questa è la soluzione più pulita se gestisci il server. Apri il tuo file php.ini. Su Linux il percorso è solitamente uno di questi:
/etc/php/8.3/fpm/php.iniper PHP-FPM, la configurazione più comune dietro nginx e Apache moderno/etc/php/8.3/apache2/php.iniper Apache con mod_php/etc/php/8.3/cli/php.iniper la riga di comando, di cui solitamente non ti importa in questo contesto
Il numero di versione nel percorso corrisponde a quale PHP stai eseguendo. Trova la riga che recita:
expose_php = OnCambiala in:
expose_php = OffSalva il file e riavvia il processo PHP affinché la modifica abbia effetto. Per PHP-FPM è così:
sudo systemctl restart php8.3-fpmPer Apache con mod_php:
sudo systemctl restart apache2Per verificare, esegui un rapido curl contro il tuo sito e guarda gli header:
curl -I https://tuodominio.comLa riga X-Powered-By dovrebbe essere completamente sparita. Se è ancora lì, hai modificato il file php.ini sbagliato (spesso ce n'è più di uno). Controlla quale viene effettivamente caricato con php --ini sulla riga di comando, o piazzando temporaneamente una chiamata phpinfo() sul sito (e rimuovendola subito dopo, per favore).
Opzione 2: impostare expose_php per sito tramite .user.ini
Non tutti hanno accesso al php.ini globale. Su molti shared host puoi comunque influenzare le impostazioni PHP tramite un file chiamato .user.ini, che posizioni nella document root del tuo sito. Crea (o modifica) un .user.ini con:
expose_php = OffPHP controlla questo file a ogni richiesta, ma memorizza in cache il risultato per impostazione predefinita per diversi minuti (controllato da user_ini.cache_ttl, di solito 300 secondi). Quindi dagli cinque minuti, cancella eventuali cache edge e poi controlla di nuovo i tuoi header.
Una parola di cautela: non tutti gli host permettono di sovrascrivere expose_php da .user.ini. La direttiva ha modalità PHP_INI_PERDIR, che tecnicamente lo consente, ma alcuni host whitelistano esplicitamente cosa può essere modificato. Se l'impostazione non ha effetto dopo diversi minuti, probabilmente il tuo host la blocca. Vai all'Opzione 5.
Opzione 3: rimuovere l'header in Apache
Puoi anche eliminare l'header a livello di webserver, il che funziona indipendentemente da cosa fa internamente PHP. Nella tua configurazione Apache (o nel .htaccess del sito, se AllowOverride lo consente), aggiungi:
<IfModule mod_headers.c>
Header unset X-Powered-By
Header always unset X-Powered-By
</IfModule>La ragione per usare sia unset sia always unset è che la versione regolare si applica solo alle risposte 2xx riuscite. La variante always si applica anche alle risposte di errore come le pagine 500, che è esattamente il posto dove non vuoi che la versione trapeli.
mod_headers deve essere abilitato perché questo funzioni. Su Debian e Ubuntu:
sudo a2enmod headers
sudo systemctl reload apache2Se sei su shared hosting con Apache, la variante .htaccess funziona quasi sempre, perché la maggior parte degli host abilita mod_headers e lo consente tramite AllowOverride All. Se la regola non ha effetto, chiedi al tuo host se mod_headers è disponibile.
Opzione 4: rimuovere l'header in nginx
Su nginx, l'equivalente va nel blocco server del tuo sito, o in un blocco http generale se lo vuoi a livello di sito:
more_clear_headers 'X-Powered-By';Piccola riserva: more_clear_headers richiede il modulo headers-more-nginx-module, che non fa parte del pacchetto nginx standard su ogni distribuzione. Debian e Ubuntu lo forniscono nel pacchetto nginx-extras:
sudo apt install nginx-extrasSe sei bloccato su nginx nudo senza il modulo extras, c'è una soluzione alternativa con fastcgi_hide_header. Questo è in realtà lo strumento giusto se l'header proviene da PHP-FPM (che nella maggior parte dei casi è il caso):
location ~ \.php$ {
# your usual fastcgi_pass and params
fastcgi_hide_header X-Powered-By;
}Ricarica nginx con sudo nginx -t && sudo systemctl reload nginx e verifica con curl -I.
Opzione 5: caso speciale, WordPress
WordPress stesso non imposta X-Powered-By. L'header viene da PHP. Quindi tutto sopra si applica invariato: disattivare expose_php o eliminare l'header in Apache o nginx lo risolve anche per un sito WordPress. Ma WordPress ti dà due angoli aggiuntivi che vale la pena conoscere.
Sopprimere l'header da WordPress. Se non hai accesso alla configurazione del webserver e .user.ini non funziona sul tuo host, puoi rimuovere l'header in modo programmatico tramite un plugin must-use. Crea un file in wp-content/mu-plugins/remove-powered-by.php (crea la cartella mu-plugins se non esiste ancora) e mettici questo:
<?php
/**
* Plugin Name: Remove X-Powered-By
* Description: Strips the X-Powered-By header from every WordPress response.
*/
if (!defined('ABSPATH')) {
exit;
}
add_action('send_headers', function () {
if (function_exists('header_remove')) {
header_remove('X-Powered-By');
}
});Due cose di cui essere consapevoli con questo approccio. Primo, header_remove() funziona solo se PHP non ha ancora iniziato a inviare il corpo della risposta. L'hook send_headers si attiva al momento giusto per qualsiasi pagina WordPress normale, quindi funziona per post, pagine, admin e REST API. Non aiuta per richieste che non raggiungono mai WordPress, come chiamate dirette a file statici o pagine in cache servite da un reverse proxy. Secondo, alcuni host eseguono un reverse proxy (Cloudflare, Varnish, Nginx davanti ad Apache) che legge l'header dalla risposta del backend e poi lo inoltra invariato. Se l'header appare ancora nel tuo browser dopo aver installato questo plugin, il proxy lo sta memorizzando in cache. Cancella la cache e, idealmente, elimina l'header anche a livello di proxy.
Anche WordPress espone la propria versione. Già che ci sei, probabilmente vuoi anche gestire il tag <meta name="generator" content="WordPress X.Y.Z"> che WordPress mette nell'head HTML, e i parametri ?ver=X.Y.Z sui file CSS e JS. Non sono la stessa cosa dell'header della versione PHP, ma trapelano la versione esatta di WordPress, che è similmente utile per un aggressore. Lo snippet sottostante gestisce entrambi:
<?php
// Remove the generator meta tag
remove_action('wp_head', 'wp_generator');
// Strip the WordPress version from RSS feeds, admin, etc.
add_filter('the_generator', '__return_empty_string');
// Remove ?ver=X.Y.Z from CSS and JS file URLs
add_filter('style_loader_src', function ($src) {
return remove_query_arg('ver', $src);
}, 9999);
add_filter('script_loader_src', function ($src) {
return remove_query_arg('ver', $src);
}, 9999);Nota che eliminare il parametro ?ver= ha un effetto collaterale: il browser non può più determinare in base al solo URL quando un file CSS o JS è stato modificato. Se ti affidi a questo per il cache-busting, ometti il filtro o sostituisci ver con un hash del contenuto del file.
Opzione 6: Cloudflare, Sucuri e altri reverse proxy
Se sei dietro Cloudflare o un CDN simile, puoi eliminare l'header un livello più in là, il che è bello perché copre ogni sito che instradi attraverso la stessa zona senza toccare l'origine. In Cloudflare questo accade tramite una Transform Rule di tipo "Modify Response Header":
- Nome regola: "Remove X-Powered-By"
- Quando le richieste in ingresso corrispondono:
Hostname equals tuodominio.com(o un wildcard se lo vuoi a livello di zona) - Poi: Rimuovi header
X-Powered-By
Sucuri, BunnyCDN, Fastly e la maggior parte degli altri reverse proxy offrono un equivalente. La dicitura esatta nella dashboard varia, ma la funzione che cerchi è sempre "modify response headers" o "edge rules".
Altri header che vale la pena controllare mentre ci sei
PHP non è l'unico che tende a essere rumoroso su se stesso negli header di risposta. Mentre hai curl aperto, scansiona gli header per uno dei seguenti:
Server: Apache/2.4.58 (Ubuntu)rivela la versione patch di Apache e il sapore del tuo OS. Sopprimi tramiteServerTokens ProdeServerSignature Offin Apache, oserver_tokens offin nginx.X-Powered-CMS,X-Generator,X-Drupal-Cacheo simile. Solitamente impostato da plugin o moduli specifici del CMS. Rimuovi allo stesso modo diX-Powered-By.X-AspNet-VersionoX-AspNetMvc-Version. Rilevante solo se qualcosa nella tua stack tocca .NET, ma vale la pena conoscere.
InspectWP li segnala tutti nella sezione header di sicurezza del tuo report. Una volta che hai nascosto la versione PHP, eseguire una nuova scansione è il modo più rapido per vedere quali altri piccoli indizi sono ancora in linea.
Verifica e hai finito
Dopo qualsiasi percorso tu abbia scelto, fai un'ultima verifica:
- Apri un terminale ed esegui
curl -I https://tuodominio.com. - Guarda l'output. Non dovrebbe esserci affatto una riga
X-Powered-By. - Ripeti per un URL admin (per WordPress prova
wp-login.php) e per una pagina di errore (aggiungi una stringa casuale all'URL per attivare un 404). L'header dovrebbe rimanere sparito ovunque. - Se usi Cloudflare o una cache, testa anche da un IP fresco o con cache-bypass, in modo da sapere che stai guardando una risposta live e non in cache.
- Esegui una nuova scansione InspectWP. Il controllo della versione PHP nella sezione Hosting ora ricadrà su altri metodi di rilevamento o segnerà la versione come non rivelata, che è quello che vuoi.
Nascondere la versione PHP non sostituisce il mantenere aggiornato PHP. È un piccolo passo in un quadro di hardening più grande. Ma è il tipo di vittoria rapida che richiede cinque minuti, rimuove un pezzo gratuito di informazione che gli aggressori altrimenti raccolgono per niente, e ti costa esattamente zero in cambio.