El hardening de GLPI rara vez cae por un zero-day. Cae por el instalador olvidado, por el webroot apuntando al directorio equivocado y por la cookie de sesión sin la marca Secure detrás del proxy. Esta guía es el checklist que aplicamos en el mantenimiento de entornos de clientes: los comandos, el virtual host y las consultas de diagnóstico que usamos para cerrar la superficie de ataque de un GLPI en producción.
Por dónde se compromete de verdad un GLPI
En el mantenimiento de decenas de entornos el patrón se repite: el compromiso casi nunca viene de un fallo exótico del core. Viene de la configuración. Los cuatro vectores que más encontramos, en orden, son: (1) install/install.php nunca eliminado tras el despliegue; (2) el DocumentRoot apuntando a la raíz de la instalación en vez de a public/, lo que deja files/_log/ y las copias de configuración servibles como texto; (3) credenciales por defecto (glpi/glpi) todavía activas; y (4) el TLS terminando en el proxy inverso con el backend en HTTP plano y la cookie de sesión sin Secure. Ninguno necesita un exploit, solo un curl. Empieza la auditoría probando exactamente esto:
# ¿El instalador sigue en línea? (esperado: 404)
curl -s -o /dev/null -w "%{http_code}\n" https://glpi.cliente.com/install/install.php
# ¿Log de errores expuesto? (esperado: 403/404, NUNCA 200 con contenido)
curl -s https://glpi.cliente.com/files/_log/php-errors.log | head
# ¿Copia de config filtrando la credencial de la base de datos? (esperado: nada)
curl -s https://glpi.cliente.com/config/config_db.php.bak | head
Si cualquiera de estos devuelve contenido, detén todo: el entorno está expuesto ahora, y corregirlo es prioridad cero.
1. Cierra la superficie: webroot en public/ y sin restos de instalación
- Apunta el
DocumentRoot(orooten Nginx) a/var/www/glpi/public, nunca a/var/www/glpi. Desde GLPI 10 solo la carpetapublic/debe ser el webroot. - Elimina
install/install.phptras terminar la instalación o la actualización. La actualización vuelve a crear ese archivo: el error común es actualizar GLPI y olvidar eliminarlo de nuevo. - Asegúrate de que
config/,files/y cualquier.bako.gitqueden fuera del alcance del servidor web.
2. Virtual host: raíz correcta, headers y solo index.php ejecutando PHP
El estándar de GLPI 10 es enrutar todo por public/index.php y no dejar que ningún otro .php se ejecute directamente. Este es el server block que usamos como base en Nginx:
# /etc/nginx/sites-available/glpi.conf
server {
listen 443 ssl;
http2 on;
server_name glpi.cliente.com;
root /var/www/glpi/public; # nunca /var/www/glpi
index index.php;
ssl_certificate /etc/letsencrypt/live/glpi.cliente.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/glpi.cliente.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Cabeceras de seguridad
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;
}
# Solo index.php enruta 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;
}
# Cualquier otro .php: denegar
location ~ \.php$ {
return 404;
}
}
En Apache el equivalente es DocumentRoot /var/www/glpi/public con el bloque <Directory> restringido y las mismas cabeceras vía Header set. Un aviso de campo: habilita Strict-Transport-Security (HSTS) solo después de que el certificado esté al 100%. Con HSTS activo y un TLS roto bloqueas todo el dominio en el navegador de los usuarios durante todo el max-age.
3. Sesión y cookies: la fuga silenciosa detrás del proxy
Este es el hallazgo que más pasa desapercibido. Cuando el TLS termina en el proxy (Nginx/HAProxy) y GLPI corre por HTTP en el backend, la cookie de sesión debe seguir marcada como Secure y HttpOnly. En Configuración > General, en la pestaña de seguridad, fuerza cookies seguras y una política de contraseñas (longitud mínima, complejidad, caducidad). Sin eso, la cookie de sesión viaja en el salto interno HTTP sin Secure, y basta con capturar el tráfico de ese segmento para secuestrar la sesión de un técnico conectado. Ajusta también el tiempo de vida de la sesión a algo acorde con el riesgo del entorno, en vez del valor largo por defecto.
4. Cuentas y autenticación
Cambia de inmediato las contraseñas de glpi, tech, normal y post-only, o desactiva las que no uses. Ejecuta este diagnóstico directo en la base de datos para mapear lo que aún depende de una contraseña local (authtype = 1) y es candidato a MFA/SSO o desactivación:
-- Cuentas por defecto de GLPI aún activas
SELECT name, is_active, last_login
FROM glpi_users
WHERE name IN ('glpi','tech','normal','post-only');
-- Todas las cuentas con contraseña LOCAL activa (authtype = 1)
-- authtype: 1=local 2=correo 3=LDAP 4=externa 5=CAS
SELECT id, name, is_active, last_login
FROM glpi_users
WHERE authtype = 1 AND is_active = 1
ORDER BY last_login;
La recomendación para producción: SSO (Azure AD/Entra ID u otro proveedor) con MFA, y login local restringido a una única cuenta de emergencia (break-glass) con contraseña fuerte guardada en un gestor. El core de GLPI no tiene MFA nativo ni bloqueo por intentos: para MFA usa un plugin adecuado o el propio SSO; para fuerza bruta, pon GLPI detrás de fail2ban leyendo el log de acceso del proxy. Consulta también nuestra guía de SSO con Azure AD/Entra ID.
5. Permisos: código de solo lectura, escritura solo donde hace falta
El principio es simple: el usuario de PHP-FPM (www-data) debe leer el código, pero nunca escribirlo. El único árbol con escritura es GLPI_VAR_DIR. Así, incluso un fallo de la aplicación no puede persistir código malicioso en el directorio servido.
| Directorio | Rol | Dueño:grupo | Escritura www-data |
|---|---|---|---|
| /var/www/glpi | Código + webroot public/ | root:www-data | No (750 / 640) |
| /etc/glpi | Config (config_db.php) | root:www-data | No (750 / 640) |
| /var/lib/glpi | GLPI_VAR_DIR (files, caché, sesiones) | www-data:www-data | Sí (750) |
| /var/log/glpi | Logs de la aplicación | www-data:www-data | Sí (750) |
Separar config/ y files/ fuera del webroot (vía GLPI_CONFIG_DIR y GLPI_VAR_DIR) es el layout que adoptamos por defecto: si algún día el webroot queda expuesto por error, la config y los datos no van con él.
6. Actualizaciones y monitoreo continuo
Mantén la versión al día. La mayoría de los CVE de GLPI ya tienen parche cuando salen en las noticias; el riesgo real es el entorno parado tres versiones atrás. Monitorea los intentos de autenticación (picos de fallos de login sugieren fuerza bruta) en Zabbix/Grafana y sigue los CVE de tu versión instalada. Para eso usamos el módulo CVE Scan, que cruza la versión de GLPI con los CVE conocidos y audita puntos del entorno. Y no trates el hardening sin plan de recuperación: consulta la guía de backup y disaster recovery.
Errores comunes que vemos en el mantenimiento
- Copia de config en el webroot: alguien guarda
config_db.php.bakantes de editar; como.bakno lo ejecuta PHP, el servidor entrega la credencial de la base de datos en texto plano. - Alias heredado sobreviviendo: el nuevo vhost apunta a
public/, pero un viejoAlias /glpisigue exponiendo la raíz. - HSTS antes del certificado: activar HSTS con un TLS incompleto bloquea el dominio en el navegador de los usuarios.
- Login local desactivado sin break-glass: cortan el login local confiando 100% en el SSO y, cuando el SSO cae, nadie entra como admin.
Hacerlo una vez es fácil; mantenerlo en decenas de entornos de clientes, con actualizaciones recurrentes que recrean install/, es trabajo de mantenimiento. Es exactamente lo que hace NexTool: el mantenimiento de GLPI de NexTool valida el webroot, endurece el virtual host, revisa permisos y ejecuta esta batería de verificación como parte del ciclo de mantenimiento.
Este conteúdo foi produzido com auxílio de inteligência artificial e revisado pela equipe Nextool Solutions.