Cartella Public di GLPI: Configurazione Sicura per la Produzione

Perché il DocumentRoot di GLPI 10 deve puntare alla cartella public: cosa resta esposto quando non lo fa, un virtual host irrobustito su Apache e Nginx, uno script di verifica e la checklist di produzione della manutenzione NexTool.

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.

PercorsoContenutoImpatto se espostoStato atteso
/config/config_db.phpHost, utente e password del databaseFuga totale del database404
/files/_log/php-errors.log, sql-errors.logRicognizione dell'app e fuga di PII404
/files/_dumps/Dump SQL generati da GLPICopia scaricabile del database404
/files/_sessions/File di sessione attiviDirottamento di sessione e furto di token CSRF404
/src/Codice sorgente PHP dell'applicazioneMappa dell'applicazione per lo sfruttamento404
/vendor/Dipendenze ComposerSfruttamento di CVE note delle librerie404
/install/install.phpProcedura guidata di installazioneRiconfigurazione e takeover dell'ambiente404 (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

  1. DocumentRoot / root che punta a .../public, mai alla radice dell'installazione.
  2. Rimuovere install/install.php non appena l'installazione/aggiornamento termina (GLPI stesso mostra un avviso rosso finché il file esiste).
  3. Forzare HTTPS con un redirect 301 dalla porta 80 e Strict-Transport-Security attivo.
  4. Irrobustimento opzionale: spostare config/ e files/ fuori dall'albero web tramite GLPI_CONFIG_DIR e GLPI_VAR_DIR, lasciando nel webroot solo public.
  5. Eseguire lo script di verifica e pretendere 404 su ogni percorso.
  6. Controllare il pannello di sicurezza di GLPI (Configurazione) dopo l'avvio - segnala pendenze come un install.php presente.

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.

Domande Frequenti

Perché il routing di GLPI 10 non dipende dal webroot per caricare le sue schermate: risolve i percorsi internamente. L'applicazione si apre e funziona normalmente con il DocumentRoot alla radice dell'installazione, quindi nulla si rompe visivamente. Il problema è silenzioso: config/, files/ e il codice sorgente diventano raggiungibili dal browser. Poiché non c'è alcun sintomo, il difetto sopravvive per anni finché qualcuno non prova gli URL giusti.

No. Il .htaccess è una funzionalità esclusiva di Apache. Nginx ignora completamente quei file, compresi quelli che GLPI sparge dentro config/ e files/ per negare l'accesso. Per questo una migrazione da Apache a Nginx che eredita il root sbagliato è pericolosa: la protezione parziale data da Apache scompare e l'esposizione diventa totale. Su Nginx, puntare il root a public è obbligatorio, non opzionale.

Sì. Finché install.php esiste, GLPI stesso mostra un avviso di sicurezza in rosso, e il file consente di riconfigurare l'ambiente. Rimuovilo (rm public/install/install.php) o rinominalo subito dopo l'installazione o l'aggiornamento. Nota: se il webroot è correttamente impostato su public, il percorso /install/install.php restituisce già 404, ma rimuovere il file è la difesa in profondità raccomandata.

Sì, ed è l'irrobustimento raccomandato per ambienti sensibili. GLPI supporta le costanti GLPI_CONFIG_DIR e GLPI_VAR_DIR per puntare config/ e files/ a directory fuori dall'albero web (per esempio /etc/glpi e /var/lib/glpi). Così, anche se in futuro qualcuno sbaglia il webroot, non c'è alcuna credenziale né log nel percorso servito dal server web.

Fai un curl verso URL che non dovrebbero mai esistere su un webroot corretto, come /config/config_db.php, /vendor/ e /files/_log/php-errors.log. Tutti devono restituire 404. Attenzione al falso positivo: config_db.php può rispondere 200 con corpo vuoto se PHP lo esegue, e questo già segnala un webroot sbagliato. Il metro è 404, non una pagina bianca.

Hai bisogno di aiuto?