Se il DocumentRoot del tuo GLPI punta alla radice dell'installazione e non alla cartella public, il file con la password del tuo database è a una richiesta GET di distanza. Da GLPI 10 questa è la decisione di sicurezza più importante del deploy - e quella che troviamo più spesso mal configurata negli ambienti che arrivano in manutenzione, proprio perché il sistema funziona perfettamente anche se configurato male.
Perché il webroot sbagliato passa inosservato
GLPI 10 continua ad aprirsi e a funzionare normalmente se punti il server web alla radice (/var/www/glpi) invece di /var/www/glpi/public. Niente si rompe a schermo, nessuno apre un ticket, ed è per questo che il difetto sopravvive per anni. Nell'onboarding in manutenzione di parchi GLPI dei clienti, questo è il ritrovamento silenzioso più comune: l'ambiente risponde bene, ma https://glpi.cliente.com/files/_log/php-errors.log restituisce il log dell'applicazione come un normale download.
Il dettaglio che emerge solo in esercizio: su Apache, la vecchia installazione era in parte salvata dai file .htaccess sparsi dentro config/, files/ e simili, che negavano l'accesso. Questo dà un falso senso di protezione. Su Nginx quei .htaccess semplicemente non esistono - Nginx non li legge mai. Quindi migrare da Apache a Nginx trascinando il root sbagliato trasforma un'esposizione parziale in totale: files/, config/ e tutto il codice sorgente diventano contenuto statico servito a chiunque. Il cambio di server web è il momento classico in cui questa bomba esplode.
Cosa resta esposto quando il DocumentRoot è sbagliato
La cartella public esiste per pubblicare solo il router (index.php), il .htaccess e i file statici (CSS, JS, immagini). Tutto il resto deve restare fuori dalla portata del browser. La tabella seguente è la matrice di verifica che usiamo: con public come webroot, tutti gli URL sotto devono restituire 404.
| Percorso | Contenuto | Impatto se esposto | Stato atteso |
|---|---|---|---|
/config/config_db.php | Host, utente e password del database | Fuga totale del database | 404 |
/files/_log/ | php-errors.log, sql-errors.log | Ricognizione dell'app e fuga di PII | 404 |
/files/_dumps/ | Dump SQL generati da GLPI | Copia scaricabile del database | 404 |
/files/_sessions/ | File di sessione attivi | Dirottamento di sessione e furto di token CSRF | 404 |
/src/ | Codice sorgente PHP dell'applicazione | Mappa dell'applicazione per lo sfruttamento | 404 |
/vendor/ | Dipendenze Composer | Sfruttamento di CVE note delle librerie | 404 |
/install/install.php | Procedura guidata di installazione | Riconfigurazione e takeover dell'ambiente | 404 (rimuovere dopo l'installazione) |
Attenzione al falso "sicuro": con PHP attivo, /config/config_db.php può rispondere 200 con corpo vuoto (il file definisce solo una classe, non stampa nulla). Un 200 vuoto non significa che è tutto a posto - significa che il file è dentro il webroot. Ciò che davvero trapela come download sono i file non PHP: files/_log/*.log, files/_dumps/*.sql e qualsiasi backup dimenticato nell'albero. Per questo il metro è 404, non "pagina vuota".
Apache: virtual host irrobustito
Il DocumentRoot punta a public, il listing delle directory è spento e l'HTTP in chiaro è reindirizzato a HTTPS. FallbackResource sostituisce il routing del .htaccess senza richiedere AllowOverride All:
# /etc/apache2/sites-available/glpi.conf
<VirtualHost *:80>
ServerName glpi.tuaazienda.com
# Niente GLPI su HTTP in chiaro
Redirect permanent / https://glpi.tuaazienda.com/
</VirtualHost>
<VirtualHost *:443>
ServerName glpi.tuaazienda.com
DocumentRoot /var/www/glpi/public
<Directory /var/www/glpi/public>
Require all granted
Options -Indexes +FollowSymLinks
# Instrada senza dipendere da AllowOverride All
FallbackResource /index.php
</Directory>
# Nega qualsiasi file nascosto (.git, .env, .htaccess)
<FilesMatch "^\.">
Require all denied
</FilesMatch>
# Intestazioni di sicurezza
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
SSLEngine On
SSLCertificateFile /etc/ssl/certs/glpi.crt
SSLCertificateKeyFile /etc/ssl/private/glpi.key
</VirtualHost>
Nginx: senza la rete di sicurezza del .htaccess
Su Nginx non c'è .htaccess a salvarti, quindi il root deve essere public fin dall'inizio. Nota due righe che quasi sempre mancano: il try_files $uri =404; dentro il blocco PHP e il blocco esplicito dei file nascosti.
# /etc/nginx/sites-available/glpi.conf
server {
listen 80;
server_name glpi.tuaazienda.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name glpi.tuaazienda.com;
root /var/www/glpi/public;
index index.php;
ssl_certificate /etc/ssl/certs/glpi.crt;
ssl_certificate_key /etc/ssl/private/glpi.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ \.php$ {
# =404 blocca l'esecuzione tramite PATH_INFO falsificato
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Nginx ignora .htaccess: nega i file nascosti a mano
location ~ /\. {
deny all;
}
}
L'errore comune qui è copiare un blocco PHP generico da internet senza il try_files $uri =404;. Senza di esso, una richiesta come /uploads/foto.jpg/x.php può essere passata a FPM tramite PATH_INFO ed eseguita, aprendo la strada a un RCE se l'attaccante riesce a scrivere un file nell'albero. Con =404, Nginx rifiuta prima di arrivare a PHP.
Verifica dopo il deploy: il test di 30 secondi
Dopo aver ricaricato il server web, esegui questo script contro il dominio reale. Ogni riga deve dire OK. Qualsiasi ERRORE è un percorso servito che non dovrebbe esistere:
#!/usr/bin/env bash
BASE="https://glpi.tuaazienda.com"
# Tutti gli URL sotto DEVONO restituire 404 su un webroot corretto
for path in \
/config/config_db.php \
/src/ \
/vendor/ \
/files/_log/php-errors.log \
/install/install.php ; do
code=$(curl -sk -o /dev/null -w '%{http_code}' "${BASE}${path}")
if [ "$code" = "404" ]; then
echo "OK ${path} -> ${code}"
else
echo "ERRORE ${path} -> ${code} (ESPOSTO)"
fi
done
In manutenzione fissiamo questo script nella checklist di onboarding di ogni nuovo cliente e lo rieseguiamo dopo ogni cambio di server web, aggiornamento o cambio di reverse proxy - perché un webroot corretto non è uno stato permanente, è qualcosa che una migrazione distratta annulla in pochi secondi.
Checklist di produzione
- DocumentRoot / root che punta a
.../public, mai alla radice dell'installazione. - Rimuovere
install/install.phpnon appena l'installazione/aggiornamento termina (GLPI stesso mostra un avviso rosso finché il file esiste). - Forzare HTTPS con un redirect 301 dalla porta 80 e
Strict-Transport-Securityattivo. - Irrobustimento opzionale: spostare
config/efiles/fuori dall'albero web tramiteGLPI_CONFIG_DIReGLPI_VAR_DIR, lasciando nel webroot solopublic. - Eseguire lo script di verifica e pretendere 404 su ogni percorso.
- Controllare il pannello di sicurezza di GLPI (Configurazione) dopo l'avvio - segnala pendenze come un
install.phppresente.
Sistemare il webroot richiede dieci minuti; scoprire che la password del database è stata scaricabile per tre anni costa molto di più. Che tu stia allestendo un GLPI nuovo, migrando da Apache a Nginx o ereditando un ambiente senza storico di configurazione, il team di manutenzione GLPI di NexTool valida il webroot, irrobustisce il virtual host ed esegue la batteria di verifica come parte dell'onboarding. Parla con noi della manutenzione GLPI.
Questo contenuto è stato prodotto con l'ausilio dell'intelligenza artificiale e revisionato dal team di Nextool Solutions.