Se o DocumentRoot do seu GLPI aponta para a raiz da instalação e não para a pasta public, o ficheiro com a palavra-passe da sua base de dados está a um GET de distância. Desde o GLPI 10 esta é a decisão de segurança mais importante do deploy - e a que mais encontramos mal configurada em ambientes que chegam para manutenção, precisamente porque o sistema funciona perfeitamente mesmo estando mal configurado.
Porque é que o webroot errado passa despercebido
O GLPI 10 continua a abrir e a operar normalmente se apontar o servidor web para a raiz (/var/www/glpi) em vez de /var/www/glpi/public. Nada quebra no ecrã, ninguém abre um pedido, e por isso a falha sobrevive durante anos. Na integração em manutenção de parques GLPI de clientes, este é o achado silencioso mais comum: o ambiente responde bem, mas https://glpi.cliente.com/files/_log/php-errors.log devolve o registo da aplicação como uma descarga normal.
O detalhe que só aparece na operação: no Apache, a instalação antiga era parcialmente salva pelos ficheiros .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ê. Assim, migrar de Apache para Nginx arrastando o root errado transforma uma exposição parcial em total: files/, config/ e todo o código-fonte passam a ser conteúdo estático servido a qualquer um. A troca de servidor web é o momento clássico em que esta bomba rebenta.
O que fica exposto quando o DocumentRoot está errado
A pasta public existe para publicar apenas o encaminhador (index.php), o .htaccess e os ficheiros estáticos (CSS, JS, imagens). Todo o resto deve ficar fora do alcance do navegador. A tabela abaixo é a matriz de verificação que usamos: com public como webroot, todos os URL abaixo devem devolver 404.
| Caminho | Conteúdo | Impacto se exposto | Estado esperado |
|---|---|---|---|
/config/config_db.php | Anfitrião, utilizador e palavra-passe da base de dados | Fuga total da base de dados | 404 |
/files/_log/ | php-errors.log, sql-errors.log | Reconhecimento da aplicação e fuga de PII | 404 |
/files/_dumps/ | Dumps SQL gerados pelo GLPI | Cópia descarregável da base de dados | 404 |
/files/_sessions/ | Ficheiros 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 CVE conhecidas de bibliotecas | 404 |
/install/install.php | Assistente de instalação | Reconfiguração e tomada 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 ficheiro apenas define uma classe, não imprime nada). Um 200 vazio não significa que está tudo bem - significa que o ficheiro está dentro do webroot. O que realmente foge como descarga são os não-PHP: files/_log/*.log, files/_dumps/*.sql e qualquer cópia de segurança esquecida na árvore. Por isso a régua é 404, não "página em branco".
Apache: virtual host endurecido
O DocumentRoot aponta para public, a listagem de diretórios está desligada e o HTTP simples é redirecionado para HTTPS. O FallbackResource substitui o encaminhamento do .htaccess sem exigir AllowOverride All:
# /etc/apache2/sites-available/glpi.conf
<VirtualHost *:80>
ServerName glpi.suaempresa.pt
# Nada de GLPI em HTTP simples
Redirect permanent / https://glpi.suaempresa.pt/
</VirtualHost>
<VirtualHost *:443>
ServerName glpi.suaempresa.pt
DocumentRoot /var/www/glpi/public
<Directory /var/www/glpi/public>
Require all granted
Options -Indexes +FollowSymLinks
# Encaminha sem depender de AllowOverride All
FallbackResource /index.php
</Directory>
# Nega qualquer ficheiro 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 que o salve, por isso o root tem de 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 ficheiros ocultos.
# /etc/nginx/sites-available/glpi.conf
server {
listen 80;
server_name glpi.suaempresa.pt;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name glpi.suaempresa.pt;
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 a 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;
}
# O Nginx não lê .htaccess: negue ficheiros ocultos à 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, um pedido como /uploads/foto.jpg/x.php pode ser passado ao FPM via PATH_INFO e executado, abrindo caminho a RCE se o atacante conseguir gravar um ficheiro 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, execute este script contra o domínio real. Cada linha tem de dizer OK. Qualquer FALHA é um caminho servido que não deveria existir:
#!/usr/bin/env bash
BASE="https://glpi.suaempresa.pt"
# Todos os URL abaixo TÊM de devolver 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 manutenção fixamos este script no checklist de integração de cada cliente novo e voltamos a executá-lo após cada troca de servidor web, atualização ou mudança de proxy inverso - porque um webroot correto não é um estado permanente, é algo que uma migração descuidada reverte em segundos.
Checklist de produção
- DocumentRoot / root a apontar 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 ficheiro existir). - Forçar HTTPS com um redirecionamento 301 da porta 80 e
Strict-Transport-Securityativo. - Endurecimento opcional: mover
config/efiles/para fora da árvore web através deGLPI_CONFIG_DIReGLPI_VAR_DIR, deixando no webroot apenaspublic. - Executar o script de verificação e exigir 404 em todos os caminhos.
- Conferir o painel de segurança do próprio GLPI (Configurar) após arrancar - assinala pendências como um
install.phppresente.
Ajustar o webroot leva dez minutos; descobrir que a palavra-passe da base de dados esteve descarregável durante três anos custa muito mais. Quer esteja a levantar um GLPI novo, a migrar de Apache para Nginx ou a herdar um ambiente sem histórico de configuração, a equipa de manutenção GLPI da NexTool valida o webroot, endurece o virtual host e executa a bateria de verificação como parte da integração. Fale connosco sobre manutenção de GLPI.
Este conteúdo foi produzido com auxílio de inteligência artificial e revisto pela equipa da Nextool Solutions.