Em resumo: Uma pasta .git pública surge quando um site é implementado com git clone ou git pull diretamente no webroot e o servidor web não bloqueia as pastas ocultas. Então é possível obter todo o código-fonte com o histórico de versões, incluindo credenciais submetidas há muito tempo. Verifique os seus domínios com uma simples chamada, bloqueie o acesso de imediato, passe o deployment para pacotes sem .git e substitua as credenciais presentes no histórico.
O código-fonte para levar
O Git é a ferramenta padrão de controlo de versões e, para equipas pequenas, é natural usá-lo também para implementar: ligar ao servidor, git pull, pronto. Funciona de forma fiável e é rápido. Mas tem um efeito secundário que muitas vezes ninguém repara.
Na pasta do projeto existe uma pasta oculta .git. Contém não só o estado atual, mas cada ficheiro em cada versão que alguma vez foi submetida, além de mensagens de commit, nomes e endereços de correio eletrónico dos programadores e referências a servidores. Se esta pasta estiver no webroot e o servidor web a servir, qualquer pessoa a pode obter.
Para os atacantes, é uma prenda. Com o código-fonte podem procurar vulnerabilidades de forma dirigida, em vez de as adivinhar do exterior. E no histórico encontram-se muitas vezes coisas que pareciam apagadas há muito: uma palavra-passe de base de dados do início, uma chave de API, um ficheiro de teste com dados de clientes.
A sua pasta .git é pública? Como verificar
A verificação demora segundos e não altera nada. Para cada domínio e subdomínio, aceda a um ficheiro que existe em qualquer repositório Git:
# Esperado: 403 ou 404
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.git/HEAD
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.git/config
# No servidor: existem pastas .git abaixo dos webroots?
find /var/www /srv/www -maxdepth 4 -type d -name ".git" 2>/dev/null
Um estado 200 significa: a pasta está acessível. Atenção também aos subdiretórios. Por vezes, o repositório não está no próprio webroot, mas numa pasta de plugin ou de tema que é gerida separadamente com o Git.
Verifique ainda os ambientes de staging e de teste. São atualizados com o Git mais vezes do que a produção e estão muitas vezes acessíveis em subdomínios fáceis de adivinhar.
De imediato: bloquear o acesso
Até o deployment ser reestruturado, uma regra no servidor web bloqueia o acesso às pastas do controlo de versões. Um bloqueio geral de ficheiros ocultos, como descrito em Proteger corretamente credenciais e ficheiros .env, cobre isto também. Quem quiser bloquear apenas o controlo de versões usa uma regra mais estreita:
location ~ /\.(git|svn|hg)(/|$) {
return 404;
}
RedirectMatch 404 /\.(git|svn|hg)(/|$)
Depois, repita a verificação acima, e com uma chamada real a partir do exterior: um teste de configuração bem-sucedido diz apenas que a sintaxe está certa, não que a regra se aplica antes de outras regras para ficheiros estáticos. Em painéis como o Plesk ou o cPanel, introduza a regra nas diretivas adicionais previstas para o efeito, para que não se perca na próxima geração da configuração.
Se a pasta esteve acessível, trate todas as credenciais que alguma vez estiveram no repositório como conhecidas. Bloquear, por si só, não basta; os valores têm de ser substituídos.
De forma duradoura: implementar sem .git no webroot
O bloqueio é uma proteção, não uma solução. A longo prazo, o controlo de versões não pertence ao servidor de produção. Caminhos provados, consoante o seu ambiente:
| Procedimento | Como funciona | Adequado para |
|---|---|---|
| Pacote de build a partir do CI | o pipeline constrói um arquivo sem .git, o servidor extrai-o para um novo diretório de release |
equipas com CI/CD |
git archive |
gera um arquivo de uma versão sem dados de versionamento | projetos pequenos sem pipeline |
rsync com exclusões |
transfere apenas os ficheiros necessários | servidores clássicos |
| Imagem de contentor | o código é copiado para a imagem durante a construção, .git excluído via .dockerignore |
operação em contentores |
Um diretório de release por versão, com uma ligação simbólica para o estado atual, tem outra vantagem: em caso de problemas, pode regressar de imediato. Para configurações com contentores, CI/CD: reconstruir imagens PHP automaticamente descreve como tornar os builds reprodutíveis. Aí deve também verificar-se que o .git não chega à imagem.
# Exemplo: gerar o pacote de uma versão sem dados de versionamento
git archive --format=tar.gz -o release.tar.gz HEAD
Outros ficheiros de projeto que não pertencem ao webroot
A pasta .git é o exemplo mais conhecido, mas não o único. Quem implementa diretórios de projeto inteiros leva para o webroot outros ficheiros que lá não têm nada que fazer:
- Pastas de ambientes de desenvolvimento como
.ideaou.vscode, em que as extensões para o upload de ficheiros guardam por vezes endereços de servidores e credenciais. composer.jsonecomposer.lock, que revelam as versões exatas de todas as bibliotecas e facilitam assim a procura de vulnerabilidades conhecidas.- Configurações para testes, ferramentas de build e pipelines, que revelam nomes de anfitriões e processos internos.
- Resíduos de sistemas operativos como
.DS_Store, que contêm listas de ficheiros de diretórios inteiros.
A solução é a mesma que para a pasta .git: servir apenas o que tem de ser público, idealmente através de uma subpasta própria como webroot. Quem de qualquer modo passa o deployment para pacotes define logo uma lista de ficheiros excluídos e verifica-a a cada nova ferramenta no projeto.
Se a pasta esteve aberta: verificar o histórico e os registos
Se se verificar que a pasta .git esteve acessível, proceda nesta ordem:
- Bloquear o acesso e repetir a verificação.
- Pesquisar o histórico para saber que segredos alguma vez foram submetidos: ficheiros de configuração,
.env, chaves, dumps de bases de dados. - Substituir as credenciais, todas as encontradas no passo 2, mesmo que hoje já não estejam no código.
- Analisar os registos de acesso para ver se ficheiros em
/.git/foram obtidos com o estado 200 e a partir de que endereços. Como fazê-lo é mostrado em Analisar os registos do servidor web após um ataque. - Verificar o código-fonte de forma dirigida, pois os atacantes conhecem-no agora. As fraquezas conhecidas devem ser corrigidas com prioridade.
- Esclarecer a proteção de dados, se o histórico continha dados pessoais, por exemplo exportações de teste. A avaliação cabe ao seu encarregado da proteção de dados.
Depois, as ferramentas de pesquisa de segredos em repositórios passam a fazer parte permanente do pipeline. A OWASP recomenda expressamente tais verificações, como hook antes da submissão e no CI.
Como ajuda concretamente a PHP SOS?
- Verificamos todos os seus domínios e subdomínios à procura de pastas de controlo de versões acessíveis.
- Bloqueamos o acesso de imediato e passamos o deployment para pacotes sem dados de versionamento.
- Pesquisamos o histórico à procura de segredos e acompanhamos a substituição dos acessos afetados.
- Analisamos os registos de acesso e documentamos se e quando foram obtidos dados.
Estas medidas fazem parte do nosso reforço de segurança de servidores para servidores web PHP.
Perguntas frequentes
Como surge uma pasta .git pública?
Normalmente através de um deployment com git clone ou git pull diretamente no webroot. A pasta .git, com todo o histórico de versões, fica então ao lado do index.php e, sem bloqueio, o servidor web serve os seus ficheiros.
Basta apagar a pasta .git?
Para o futuro, sim; para o passado, não. Se a pasta esteve acessível, o conteúdo pode já ter sido copiado. Deve por isso substituir as credenciais que alguma vez estiveram no repositório.
Ajuda remover posteriormente as credenciais do histórico do Git?
Evita que as novas cópias as contenham. Quem já obteve o repositório continua a ter as versões antigas. Decisiva é por isso a substituição das credenciais, não apenas a reescrita do histórico.
Isto afeta também outros sistemas de controlo de versões?
Sim. Também pastas como .svn ou .hg contêm dados do projeto e nunca devem estar acessíveis no webroot. Um bloqueio geral de ficheiros e pastas com ponto no início cobre todos estes casos.