La cartella wp-content/uploads è l’unico posto di un’installazione WordPress in cui il sito deve poter scrivere liberamente: ci finiscono immagini, PDF, file generati dai plugin. Proprio per questo è anche il posto preferito per le webshell, piccoli file PHP che, richiamati dal browser, permettono a chi attacca di eseguire comandi, caricare altri file e navigare nel server.
Perché gli upload sono il bersaglio
Una vulnerabilità di caricamento file in un plugin, un modulo di contatto mal configurato o un account da autore compromesso bastano per scrivere un file dentro gli upload. Se il server esegue i file PHP anche in quella cartella, il file caricato diventa subito un punto d’accesso. Le webshell hanno spesso nomi che si confondono con il resto, come cache.php, thumb.php o un nome casuale dentro una cartella dell’anno e del mese, e a volte una data di modifica falsificata per sembrare vecchie.
Nella cartella degli upload di un sito WordPress normale non dovrebbe esserci nessun file PHP. Le eccezioni sono rare, riguardano pochi plugin e vanno verificate una per una.
Trovare i file eseguibili
La ricerca va fatta su tutte le estensioni che il server potrebbe interpretare come PHP, non solo su .php:
find wp-content/uploads -type f \( -iname '*.php*' -o -iname '*.phtml' \
-o -iname '*.phar' -o -iname '.htaccess' -o -iname '.user.ini' \)
Nell’elenco vanno considerati anche i file .htaccess e .user.ini: un attacco li crea per riattivare l’esecuzione di PHP o per caricare un file a ogni richiesta tramite auto_prepend_file. E un file con estensione da immagine non è per forza innocuo: può contenere codice PHP che un altro file include.
Bloccare l’esecuzione di PHP
Con Apache, e con LiteSpeed che ne legge i file .htaccess, basta un file .htaccess dentro la cartella degli upload:
# wp-content/uploads/.htaccess (Apache 2.4 e LiteSpeed)
<FilesMatch "\.(?i:php[0-9]?|phtml|phar)$">
Require all denied
</FilesMatch>
Con NGINX la regola va nella configurazione del virtual host, e deve comparire prima del blocco generico che passa i file PHP a PHP-FPM, perché NGINX usa la prima espressione regolare che corrisponde:
location ~* ^/wp-content/uploads/.*\.(php[0-9]?|phtml|phar)$ {
deny all;
}
Dopo la modifica conviene verificare davvero il risultato: si crea un file PHP di prova negli upload, lo si richiama dal browser e si controlla che la risposta sia un errore 403 e non l’esecuzione del codice. Poi il file di prova va cancellato.
Cosa il blocco non fa
Bloccare l’esecuzione diretta è una misura importante, ma ha dei limiti precisi. Non cancella le webshell già presenti, che vanno comunque trovate e rimosse. Non impedisce che un file PHP in un’altra cartella includa un file nascosto negli upload, perché l’inclusione avviene dentro PHP e non passa dal server web. E se la regola è in un .htaccess che l’utente del sito può modificare, un attacco che ha già accesso in scrittura può rimuoverla: per questo, dove possibile, è meglio metterla nella configurazione del server.
Il blocco va quindi visto come uno strato in più, insieme agli aggiornamenti dei plugin, ai permessi corretti e al controllo dei file. Se ne trovi uno sospetto e non sai cosa faccia, non aprirlo dal browser: scrivici e lo analizziamo senza eseguirlo.