Si le DocumentRoot de votre GLPI pointe vers la racine de l'installation et non vers le dossier public, le fichier contenant le mot de passe de votre base de données est à une requête GET. Depuis GLPI 10 c'est la décision de sécurité la plus importante du déploiement - et celle que nous trouvons le plus souvent mal configurée dans les environnements confiés en maintenance, précisément parce que le système fonctionne parfaitement même mal configuré.
Pourquoi le mauvais webroot passe inaperçu
GLPI 10 continue de s'ouvrir et de fonctionner normalement si vous pointez le serveur web vers la racine (/var/www/glpi) au lieu de /var/www/glpi/public. Rien ne casse à l'écran, personne n'ouvre de ticket, et c'est pourquoi la faille survit des années. Lors de la reprise en maintenance de parcs GLPI clients, c'est le constat silencieux le plus fréquent : l'environnement répond bien, mais https://glpi.client.com/files/_log/php-errors.log renvoie le journal de l'application comme un simple téléchargement.
Le détail qui n'apparaît qu'en exploitation : sur Apache, l'ancienne installation était partiellement sauvée par les fichiers .htaccess disséminés dans config/, files/ et consorts, qui refusaient l'accès. Cela donne un faux sentiment de protection. Sur Nginx ces .htaccess n'existent tout simplement pas - Nginx ne les lit jamais. Migrer d'Apache vers Nginx en traînant le mauvais root transforme donc une exposition partielle en exposition totale : files/, config/ et tout le code source deviennent du contenu statique servi à tout le monde. Le changement de serveur web est le moment classique où cette bombe explose.
Ce qui est exposé quand le DocumentRoot est faux
Le dossier public existe pour ne publier que le routeur (index.php), le .htaccess et les fichiers statiques (CSS, JS, images). Tout le reste doit rester hors de portée du navigateur. Le tableau ci-dessous est la matrice de vérification que nous utilisons : avec public comme webroot, toutes les URL ci-dessous doivent renvoyer 404.
| Chemin | Contenu | Impact en cas d'exposition | Statut attendu |
|---|---|---|---|
/config/config_db.php | Hôte, utilisateur et mot de passe de la base | Fuite totale de la base de données | 404 |
/files/_log/ | php-errors.log, sql-errors.log | Reconnaissance de l'application et fuite de PII | 404 |
/files/_dumps/ | Dumps SQL produits par GLPI | Copie téléchargeable de la base | 404 |
/files/_sessions/ | Fichiers de session actifs | Vol de session et de jeton CSRF | 404 |
/src/ | Code source PHP de l'application | Cartographie de l'application pour l'exploitation | 404 |
/vendor/ | Dépendances Composer | Exploitation de CVE connues des bibliothèques | 404 |
/install/install.php | Assistant d'installation | Reconfiguration et prise de contrôle | 404 (à supprimer après l'installation) |
Attention au faux "sûr" : avec PHP actif, /config/config_db.php peut répondre 200 avec un corps vide (le fichier ne fait que définir une classe, il n'affiche rien). Un 200 vide ne veut pas dire que tout va bien - il veut dire que le fichier est à l'intérieur du webroot. Ce qui fuit vraiment en téléchargement, ce sont les fichiers non PHP : files/_log/*.log, files/_dumps/*.sql et toute sauvegarde oubliée dans l'arborescence. C'est pourquoi la règle est 404, pas "page vide".
Apache : hôte virtuel durci
Le DocumentRoot pointe vers public, le listing de répertoire est désactivé et le HTTP en clair est redirigé vers HTTPS. FallbackResource remplace le routage du .htaccess sans exiger AllowOverride All :
# /etc/apache2/sites-available/glpi.conf
<VirtualHost *:80>
ServerName glpi.votreentreprise.com
# Pas de GLPI en HTTP clair
Redirect permanent / https://glpi.votreentreprise.com/
</VirtualHost>
<VirtualHost *:443>
ServerName glpi.votreentreprise.com
DocumentRoot /var/www/glpi/public
<Directory /var/www/glpi/public>
Require all granted
Options -Indexes +FollowSymLinks
# Route sans dépendre de AllowOverride All
FallbackResource /index.php
</Directory>
# Refuse tout fichier caché (.git, .env, .htaccess)
<FilesMatch "^\.">
Require all denied
</FilesMatch>
# En-têtes de sécurité
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 : sans le filet de sécurité du .htaccess
Sur Nginx il n'y a pas de .htaccess pour vous sauver, donc le root doit être public dès le départ. Repérez deux lignes qui manquent presque toujours : le try_files $uri =404; dans le bloc PHP et le blocage explicite des fichiers cachés.
# /etc/nginx/sites-available/glpi.conf
server {
listen 80;
server_name glpi.votreentreprise.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name glpi.votreentreprise.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 bloque l'exécution via un PATH_INFO forgé
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 ignore .htaccess : refusez les fichiers cachés à la main
location ~ /\. {
deny all;
}
}
L'erreur courante ici est de copier un bloc PHP générique trouvé sur internet sans le try_files $uri =404;. Sans lui, une requête comme /uploads/photo.jpg/x.php peut être transmise à FPM via PATH_INFO et exécutée, ouvrant la voie à un RCE si l'attaquant parvient à écrire un fichier dans l'arborescence. Avec =404, Nginx refuse avant d'atteindre PHP.
Vérification après déploiement : le test de 30 secondes
Après avoir rechargé le serveur web, lancez ce script contre le domaine réel. Chaque ligne doit dire OK. Tout ÉCHEC est un chemin servi qui ne devrait pas exister :
#!/usr/bin/env bash
BASE="https://glpi.votreentreprise.com"
# Toutes les URL ci-dessous DOIVENT renvoyer 404 sur un webroot correct
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 "ECHEC ${path} -> ${code} (EXPOSE)"
fi
done
En maintenance, nous épinglons ce script à la checklist d'intégration de chaque nouveau client et le relançons après chaque changement de serveur web, mise à jour ou changement de reverse proxy - car un webroot correct n'est pas un état permanent, c'est quelque chose qu'une migration négligente annule en quelques secondes.
Checklist de production
- DocumentRoot / root pointant vers
.../public, jamais vers la racine de l'installation. - Supprimer
install/install.phpdès la fin de l'installation ou de la mise à jour (GLPI affiche lui-même un avertissement rouge tant que le fichier existe). - Forcer HTTPS avec une redirection 301 depuis le port 80 et
Strict-Transport-Securityactivé. - Durcissement optionnel : déplacer
config/etfiles/hors de l'arborescence web viaGLPI_CONFIG_DIRetGLPI_VAR_DIR, ne laissant quepublicdans le webroot. - Lancer le script de vérification et exiger 404 sur chaque chemin.
- Vérifier le panneau de sécurité de GLPI (Configuration) après le démarrage - il signale les points en attente comme un
install.phpprésent.
Corriger le webroot prend dix minutes ; découvrir que le mot de passe de la base était téléchargeable pendant trois ans coûte bien plus cher. Que vous montiez un GLPI neuf, migriez d'Apache vers Nginx ou héritiez d'un environnement sans historique de configuration, l'équipe de maintenance GLPI de NexTool valide le webroot, durcit l'hôte virtuel et exécute la batterie de vérification dans le cadre de l'intégration. Parlez-nous de la maintenance GLPI.
Ce contenu a été produit avec l'aide de l'intelligence artificielle et revu par l'équipe de Nextool Solutions.