Sécurité de GLPI : durcissement et bonnes pratiques

Checklist pratique de durcissement de GLPI en production : racine web en public/, virtual host Nginx, en-têtes de sécurité, cookie de session Secure derrière le proxy, permissions, comptes et diagnostic SQL - directement issu de la maintenance d'environnements NexTool.

Le durcissement de GLPI échoue rarement à cause d'un zero-day. Il échoue à cause de l'installeur oublié, de la racine web pointée vers le mauvais répertoire et du cookie de session sans l'attribut Secure derrière le proxy. Ce guide est la checklist que nous appliquons en maintenance d'environnements clients : les commandes, le virtual host et les requêtes de diagnostic que nous utilisons pour verrouiller la surface d'attaque d'un GLPI en production.

Comment un GLPI est réellement compromis

Sur des dizaines d'environnements maintenus, le schéma se répète : la compromission ne vient presque jamais d'une faille exotique du core. Elle vient de la configuration. Les quatre vecteurs les plus fréquents, dans l'ordre, sont : (1) install/install.php jamais supprimé après le déploiement ; (2) le DocumentRoot pointant vers la racine de l'installation au lieu de public/, ce qui laisse files/_log/ et les sauvegardes de configuration servables en texte ; (3) des identifiants par défaut (glpi/glpi) toujours actifs ; et (4) le TLS terminant sur le proxy inverse avec le backend en HTTP simple et le cookie de session sans Secure. Aucun ne requiert d'exploit, juste un curl. Commencez l'audit en testant exactement ceci :

# Installeur toujours en ligne ? (attendu : 404)
curl -s -o /dev/null -w "%{http_code}\n" https://glpi.client.com/install/install.php

# Log d'erreurs exposé ? (attendu : 403/404, JAMAIS 200 avec contenu)
curl -s https://glpi.client.com/files/_log/php-errors.log | head

# Sauvegarde de config fuyant les identifiants de la base ? (attendu : rien)
curl -s https://glpi.client.com/config/config_db.php.bak | head

Si l'un d'eux renvoie du contenu, arrêtez tout : l'environnement est exposé maintenant, et le corriger est la priorité zéro.

1. Fermez la surface : racine web en public/ et aucun reste d'installation

  1. Pointez le DocumentRoot (ou root sur Nginx) vers /var/www/glpi/public, jamais vers /var/www/glpi. Depuis GLPI 10, seul le dossier public/ doit être la racine web.
  2. Supprimez install/install.php après avoir terminé l'installation ou la mise à niveau. La mise à niveau recrée ce fichier : l'erreur courante est de mettre GLPI à jour et d'oublier de le supprimer de nouveau.
  3. Assurez-vous que config/, files/ et tout .bak ou .git restent hors de portée du serveur web.

2. Virtual host : bonne racine, en-têtes et seul index.php exécutant PHP

Le standard de GLPI 10 est de tout router via public/index.php et de ne laisser aucun autre .php s'exécuter directement. Voici le server block que nous utilisons comme base sur Nginx :

# /etc/nginx/sites-available/glpi.conf
server {
    listen 443 ssl;
    http2 on;
    server_name glpi.client.com;

    root /var/www/glpi/public;   # jamais /var/www/glpi
    index index.php;

    ssl_certificate     /etc/letsencrypt/live/glpi.client.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/glpi.client.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    # En-tetes de securite
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    # Seul index.php route PHP
    location ~ ^/index\.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    # Tout autre .php : refuser
    location ~ \.php$ {
        return 404;
    }
}

Sur Apache, l'équivalent est DocumentRoot /var/www/glpi/public avec un bloc <Directory> restreint et les mêmes en-têtes via Header set. Une note de terrain : n'activez Strict-Transport-Security (HSTS) qu'une fois le certificat pleinement fonctionnel. Avec HSTS activé et un TLS cassé, vous verrouillez tout le domaine dans le navigateur des utilisateurs pendant toute la durée du max-age.

3. Session et cookies : la fuite silencieuse derrière le proxy

C'est le constat qui passe le plus souvent inaperçu. Quand le TLS se termine sur le proxy (Nginx/HAProxy) et que GLPI tourne en HTTP sur le backend, le cookie de session doit rester marqué Secure et HttpOnly. Dans Configuration > Général, onglet de sécurité, forcez les cookies sécurisés et une politique de mot de passe (longueur minimale, complexité, expiration). Sans cela, le cookie de session circule sur le saut interne HTTP sans Secure, et capturer le trafic sur ce segment suffit à détourner la session d'un technicien connecté. Ajustez aussi la durée de vie de session à une valeur cohérente avec le risque de l'environnement, plutôt que le long réglage par défaut.

4. Comptes et authentification

Changez immédiatement les mots de passe de glpi, tech, normal et post-only, ou désactivez ceux que vous n'utilisez pas. Lancez ce diagnostic directement sur la base pour cartographier ce qui dépend encore d'un mot de passe local (authtype = 1) et qui est candidat au MFA/SSO ou à la désactivation :

-- Comptes GLPI par defaut encore actifs
SELECT name, is_active, last_login
FROM   glpi_users
WHERE  name IN ('glpi','tech','normal','post-only');

-- Tous les comptes avec un mot de passe LOCAL actif (authtype = 1)
-- authtype : 1=local  2=mail  3=LDAP  4=externe  5=CAS
SELECT id, name, is_active, last_login
FROM   glpi_users
WHERE  authtype = 1 AND is_active = 1
ORDER  BY last_login;

La recommandation en production : SSO (Azure AD/Entra ID ou autre fournisseur) avec MFA, et login local restreint à un unique compte d'urgence (break-glass) avec un mot de passe fort conservé dans un coffre. Le core de GLPI n'a ni MFA natif ni verrouillage par tentatives : pour le MFA, utilisez un plugin adapté ou le SSO lui-même ; pour la force brute, placez GLPI derrière fail2ban lisant le log d'accès du proxy. Voyez aussi notre guide du SSO avec Azure AD/Entra ID.

5. Permissions : code en lecture seule, écriture uniquement où nécessaire

Le principe est simple : l'utilisateur PHP-FPM (www-data) doit lire le code, mais jamais l'écrire. La seule arborescence en écriture est GLPI_VAR_DIR. Ainsi, même une faille applicative ne peut pas persister de code malveillant dans le répertoire servi.

RépertoireRôlePropriétaire:groupeÉcriture www-data
/var/www/glpiCode + racine web public/root:www-dataNon (750 / 640)
/etc/glpiConfig (config_db.php)root:www-dataNon (750 / 640)
/var/lib/glpiGLPI_VAR_DIR (files, cache, sessions)www-data:www-dataOui (750)
/var/log/glpiLogs de l'applicationwww-data:www-dataOui (750)

Sortir config/ et files/ de la racine web (via GLPI_CONFIG_DIR et GLPI_VAR_DIR) est le schéma que nous adoptons par défaut : si un jour la racine web est exposée par erreur, la config et les données ne partent pas avec.

6. Mises à jour et surveillance continue

Gardez la version à jour. La plupart des CVE de GLPI ont déjà un correctif au moment où elles font l'actualité ; le vrai risque est l'environnement bloqué trois versions en arrière. Surveillez les tentatives d'authentification (des pics d'échecs de connexion suggèrent une attaque par force brute) dans Zabbix/Grafana et suivez les CVE de votre version installée. Pour cela nous utilisons le module CVE Scan, qui croise la version de GLPI avec les CVE connues et audite des points de l'environnement. Et ne traitez pas le durcissement sans plan de reprise : consultez le guide de sauvegarde et de reprise après sinistre.

Erreurs courantes que nous voyons en maintenance

  • Sauvegarde de config dans la racine web : quelqu'un enregistre config_db.php.bak avant d'éditer ; comme .bak n'est pas exécuté par PHP, le serveur livre les identifiants de la base en texte clair.
  • Alias hérité qui survit : le nouveau vhost pointe vers public/, mais un ancien Alias /glpi expose toujours la racine.
  • HSTS avant le certificat : activer HSTS avec un TLS incomplet verrouille le domaine dans le navigateur des utilisateurs.
  • Login local désactivé sans break-glass : on coupe le login local en se fiant à 100% au SSO et, quand le SSO tombe, personne ne peut se connecter en admin.

Le faire une fois est facile ; le maintenir sur des dizaines d'environnements clients, avec des mises à niveau récurrentes qui recréent install/, est un travail de maintenance. C'est exactement ce que fait NexTool : la maintenance GLPI de NexTool valide la racine web, durcit le virtual host, revoit les permissions et exécute cette batterie de vérification dans le cadre du cycle de maintenance.


Este conteúdo foi produzido com auxílio de inteligência artificial e revisado pela equipe Nextool Solutions.

Questions fréquentes

Faites un curl vers /install/install.php, /files/_log/php-errors.log et /config/config_db.php.bak. La réponse attendue est 403 ou 404 ; tout 200 avec contenu signifie une exposition. La cause est presque toujours le DocumentRoot pointant vers la racine de l'installation au lieu de public/.

Cela fonctionne, mais c'est peu sûr. Avec la mauvaise racine, des fichiers comme files/_log/ et les sauvegardes de config deviennent du texte servable par le serveur web. Depuis GLPI 10, public/ est la seule racine web correcte car elle isole index.php du reste de l'installation.

Pas dans le core. Placez GLPI derrière fail2ban lisant le log d'accès du proxy inverse, ou utilisez SSO/MFA pour réduire la surface. En parallèle, surveillez les pics d'échecs de connexion dans Zabbix ou Grafana, qui sont le signe typique d'une attaque par force brute.

Dans Configuration > Général, onglet sécurité, activez les cookies sécurisés ; assurez-vous que le proxy transmet X-Forwarded-Proto et que GLPI reconnaît qu'il est en HTTPS. Sans l'attribut Secure, le cookie de session circule sur le saut HTTP interne et peut être capturé sur ce segment.

Pas nativement dans le core. Utilisez un plugin MFA (TOTP) ou le SSO avec Azure AD/Entra ID ou Okta, qui fournissent déjà le MFA. Conservez toujours un compte local d'urgence (break-glass) avec un mot de passe fort, pour ne pas perdre l'accès administrateur si le SSO tombe.

Besoin d'aide ?