Se o DocumentRoot do seu GLPI aponta para a raiz da instalação e não para a pasta public, o arquivo com a senha do seu banco de dados está a um GET de distância. Desde o GLPI 10 essa é a decisão de segurança mais importante do deploy - e a que mais encontramos errada em ambientes que chegam para sustentação, justamente porque o sistema funciona perfeitamente mesmo mal configurado.
Por que o webroot errado passa despercebido
O GLPI 10 continua abrindo e operando normalmente se você apontar o servidor web para a raiz (/var/www/glpi) em vez de /var/www/glpi/public. Nada quebra na tela, ninguém abre chamado, e por isso a falha sobrevive por anos. Na sustentação de parques GLPI de clientes, esse é o achado silencioso mais comum no onboarding: o ambiente responde bem, mas https://glpi.cliente.com/files/_log/php-errors.log devolve o log da aplicação como um download comum.
O detalhe que só aparece na operação: no Apache, a instalação antiga era parcialmente salva pelos arquivos .htaccess espalhados dentro de config/, files/ e afins, que negavam o acesso. Isso dá uma falsa sensação de proteção. No Nginx esses .htaccess simplesmente não existem - o Nginx nunca os lê. Então migrar de Apache para Nginx carregando o root errado transforma uma exposição parcial em exposição total: files/, config/ e todo o código-fonte viram conteúdo estático servido para qualquer um. Troca de servidor web é o momento clássico em que essa bomba estoura.
O que fica exposto quando o DocumentRoot está errado
A pasta public existe para publicar apenas o roteador (index.php), o .htaccess e os arquivos estáticos (CSS, JS, imagens). Todo o resto deve estar fora do alcance do navegador. A tabela abaixo é a matriz de verificação que usamos: com o public como webroot, todas as URLs abaixo devem retornar 404.
| Caminho | Conteúdo | Impacto se exposto | Status esperado |
|---|---|---|---|
/config/config_db.php | Host, usuário e senha do banco | Vazamento total do banco de dados | 404 |
/files/_log/ | php-errors.log, sql-errors.log | Recon da aplicação e vazamento de PII | 404 |
/files/_dumps/ | Dumps SQL gerados pelo GLPI | Cópia do banco baixável | 404 |
/files/_sessions/ | Arquivos de sessão ativos | Sequestro de sessão e roubo de token CSRF | 404 |
/src/ | Código-fonte PHP da aplicação | Mapa da aplicação para exploração | 404 |
/vendor/ | Dependências Composer | Exploração de CVEs de bibliotecas | 404 |
/install/install.php | Assistente de instalação | Reconfiguração e takeover do ambiente | 404 (remover após instalar) |
Cuidado com o falso "seguro": com o PHP ativo, /config/config_db.php pode responder 200 com corpo vazio (o arquivo apenas define uma classe, não imprime nada). Um 200 vazio não significa que está tudo bem - significa que o arquivo está dentro do webroot. O que realmente vaza como download são os não-PHP: files/_log/*.log, files/_dumps/*.sql e qualquer backup esquecido na árvore. Por isso a régua é 404, não "página vazia".
Apache: virtual host endurecido
O DocumentRoot aponta para public, o listing de diretório é desligado e o HTTP puro é redirecionado para HTTPS. O FallbackResource substitui o roteamento do .htaccess sem exigir AllowOverride All:
# /etc/apache2/sites-available/glpi.conf
<VirtualHost *:80>
ServerName glpi.suaempresa.com
# Nada de GLPI em HTTP puro
Redirect permanent / https://glpi.suaempresa.com/
</VirtualHost>
<VirtualHost *:443>
ServerName glpi.suaempresa.com
DocumentRoot /var/www/glpi/public
<Directory /var/www/glpi/public>
Require all granted
Options -Indexes +FollowSymLinks
# Roteia sem depender de AllowOverride All
FallbackResource /index.php
</Directory>
# Nega qualquer arquivo oculto (.git, .env, .htaccess)
<FilesMatch "^\.">
Require all denied
</FilesMatch>
# Cabeçalhos de segurança
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: sem a rede de segurança do .htaccess
No Nginx não há .htaccess para salvar você, então o root tem que ser public desde o início. Repare em duas linhas que quase sempre faltam: o try_files $uri =404; dentro do bloco PHP e o bloqueio explícito de arquivos ocultos.
# /etc/nginx/sites-available/glpi.conf
server {
listen 80;
server_name glpi.suaempresa.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name glpi.suaempresa.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 impede execução via PATH_INFO forjado
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 não lê .htaccess: negue arquivos ocultos na mão
location ~ /\. {
deny all;
}
}
O erro comum aqui é copiar um bloco PHP genérico da internet, sem o try_files $uri =404;. Sem ele, uma requisição como /uploads/foto.jpg/x.php pode ser passada ao FPM via PATH_INFO e executada, abrindo caminho para RCE se o atacante conseguir gravar um arquivo na árvore. Com o =404, o Nginx recusa antes de chegar ao PHP.
Verificação pós-deploy: o teste de 30 segundos
Depois de recarregar o servidor web, rode este script contra o domínio real. Toda linha precisa dizer OK. Qualquer FALHA é um caminho servido que não deveria existir:
#!/usr/bin/env bash
BASE="https://glpi.suaempresa.com"
# Todas as URLs abaixo DEVEM retornar 404 num webroot correto
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 "FALHA ${path} -> ${code} (EXPOSTO)"
fi
done
Na sustentação nós fixamos esse script no checklist de onboarding de todo cliente novo e o rodamos de novo a cada troca de servidor web, upgrade ou mudança de proxy reverso - porque webroot correto não é estado permanente, é algo que uma migração desatenta reverte em segundos.
Checklist de produção
- DocumentRoot / root apontando para
.../public, nunca para a raiz da instalação. - Remover
install/install.phpassim que a instalação/atualização terminar (o próprio GLPI mostra um aviso vermelho enquanto o arquivo existe). - Forçar HTTPS com redirect 301 do 80 e
Strict-Transport-Securityligado. - Endurecer opcional: mover
config/efiles/para fora da árvore web viaGLPI_CONFIG_DIReGLPI_VAR_DIR, deixando o webroot só compublic. - Rodar o script de verificação e exigir 404 em todos os caminhos.
- Conferir o painel de segurança do próprio GLPI (Configurar) depois de subir - ele sinaliza pendências como o
install.phppresente.
Ajustar o webroot leva dez minutos; descobrir que a senha do banco esteve baixável por três anos custa muito mais. Se você está subindo um GLPI novo, migrando de Apache para Nginx ou herdando um ambiente sem histórico de configuração, a equipe de sustentação GLPI da NexTool valida o webroot, endurece o virtual host e roda a bateria de verificação como parte do onboarding. Fale com a gente sobre sustentação de GLPI.
Este conteúdo foi produzido com auxílio de inteligência artificial e revisado pela equipe Nextool Solutions.