Dossier Public de GLPI : Configuration Sécurisée pour la Production

Pourquoi le DocumentRoot de GLPI 10 doit pointer vers le dossier public : ce qui est exposé quand ce n'est pas le cas, un hôte virtuel durci sous Apache et Nginx, un script de vérification et la checklist de production de la maintenance NexTool.

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.

CheminContenuImpact en cas d'expositionStatut attendu
/config/config_db.phpHôte, utilisateur et mot de passe de la baseFuite totale de la base de données404
/files/_log/php-errors.log, sql-errors.logReconnaissance de l'application et fuite de PII404
/files/_dumps/Dumps SQL produits par GLPICopie téléchargeable de la base404
/files/_sessions/Fichiers de session actifsVol de session et de jeton CSRF404
/src/Code source PHP de l'applicationCartographie de l'application pour l'exploitation404
/vendor/Dépendances ComposerExploitation de CVE connues des bibliothèques404
/install/install.phpAssistant d'installationReconfiguration et prise de contrôle404 (à 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

  1. DocumentRoot / root pointant vers .../public, jamais vers la racine de l'installation.
  2. Supprimer install/install.php dès la fin de l'installation ou de la mise à jour (GLPI affiche lui-même un avertissement rouge tant que le fichier existe).
  3. Forcer HTTPS avec une redirection 301 depuis le port 80 et Strict-Transport-Security activé.
  4. Durcissement optionnel : déplacer config/ et files/ hors de l'arborescence web via GLPI_CONFIG_DIR et GLPI_VAR_DIR, ne laissant que public dans le webroot.
  5. Lancer le script de vérification et exiger 404 sur chaque chemin.
  6. Vérifier le panneau de sécurité de GLPI (Configuration) après le démarrage - il signale les points en attente comme un install.php pré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.

Questions fréquentes

Parce que le routage de GLPI 10 ne dépend pas du webroot pour charger ses écrans : il résout les chemins en interne. L'application s'ouvre et fonctionne normalement avec le DocumentRoot à la racine de l'installation, donc rien ne casse visuellement. Le problème est silencieux : config/, files/ et le code source deviennent accessibles depuis le navigateur. Comme il n'y a aucun symptôme, la faille survit des années jusqu'à ce que quelqu'un teste les bonnes URL.

Non. Le .htaccess est une fonctionnalité propre à Apache. Nginx ignore complètement ces fichiers, y compris ceux que GLPI dissémine dans config/ et files/ pour refuser l'accès. C'est pourquoi une migration Apache vers Nginx qui hérite du mauvais root est dangereuse : la protection partielle d'Apache disparaît et l'exposition devient totale. Sous Nginx, pointer le root vers public est obligatoire, pas optionnel.

Oui. Tant qu'install.php existe, GLPI affiche lui-même un avertissement de sécurité en rouge, et le fichier permet de reconfigurer l'environnement. Supprimez-le (rm public/install/install.php) ou renommez-le juste après l'installation ou la mise à jour. Note : si le webroot est correctement réglé sur public, le chemin /install/install.php renvoie déjà 404, mais supprimer le fichier est la défense en profondeur recommandée.

Oui, et c'est le durcissement recommandé pour les environnements sensibles. GLPI prend en charge les constantes GLPI_CONFIG_DIR et GLPI_VAR_DIR pour pointer config/ et files/ vers des répertoires hors de l'arborescence web (par exemple /etc/glpi et /var/lib/glpi). Ainsi, même si quelqu'un se trompe de webroot à l'avenir, il n'y a ni identifiant ni journal dans le chemin servi par le serveur web.

Faites un curl vers des URL qui ne devraient jamais exister sur un webroot correct, comme /config/config_db.php, /vendor/ et /files/_log/php-errors.log. Toutes doivent renvoyer 404. Attention au faux positif : config_db.php peut répondre 200 avec un corps vide si PHP l'exécute, ce qui signale déjà un mauvais webroot. La règle est 404, pas une page blanche.

Besoin d'aide ?