Para: qualquer pessoa rodando apps estilo Laravel (onde só public/ deveria ser exposto) em hospedagem compartilhada com Apache/.htaccess. Destilado de um incidente real (→ .env exposto ao mundo) — para proteger o seu próprio deploy.
A visão deste site: isto não é 'o erro de uma pessoa'
A cultura inicial de hospedagem compartilhada colocava tudo diretamente sob public_html. O Laravel (que espera só public/ exposto) fluiu para dentro disso, e um layout perigoso virou o padrão de fato — até a própria documentação de fornecedores coloca projetos dentro de public_html. Então a correção é processo, não vigilância: mude o padrão do seu deploy.
Acerte o posicionamento (diagrama)
✗ Perigoso (corpo na raiz pública)
public_html/ ├─ .env ← legível! ├─ .git/ vendor/ └─ public/
✓ Seguro (corpo fora, só public)
app-laravel/ ← fora do web root ├─ .env .git/ ← inalcançável └─ public/ → requerido por public_html
Qual correção serve para o seu ambiente
"Manter o .env fora da web" muda de forma conforme o tipo de servidor e as suas permissões. Encontre primeiro a sua linha e depois siga os passos abaixo.
| Ambiente | Correção permanente | Primeiros socorros | Atenção |
|---|---|---|---|
| Hospedagem compartilhada (com SSH) | Tire o corpo do app da pasta pública e deixe ali só um pequeno index.php (Passo 2) | Negar no .htaccess | Transformar a própria pasta pública em symlink retorna 500 em alguns ambientes |
| Hospedagem compartilhada (só FTP) | Reenvie o corpo do app um nível acima da pasta pública (public_html etc.) com o seu cliente FTP | Negar no .htaccess | A maioria das hospedagens permite gravar acima da pasta pública; se a sua não permitir, fale com o suporte |
| O painel de controle permite definir a pasta pública | Aponte a pasta pública direto para app-laravel/public | Não é necessário (verifique mesmo assim) | Depois de trocar, apague qualquer cópia do app que tenha ficado na pasta pública antiga |
| VPS (Apache) | Aponte o DocumentRoot do VirtualHost para public/ | Regras de negação na configuração do servidor | Com AllowOverride None (o padrão do Apache 2.4), o .htaccess não é lido |
| VPS (Nginx) | Aponte root para public/ e negue arquivos que começam com ponto | Regras de negação na configuração do servidor | O Nginx não lê .htaccess |
O objetivo é o mesmo em todos os casos: nada de .env, .git, vendor/ ou storage/ dentro da pasta pública. A documentação oficial do Laravel também avisa que mover o index.php para a raiz do projeto e servir a partir dali expõe muitos arquivos de configuração sensíveis à internet pública.
Passo 1: Primeiros socorros (rápido, reversível)
Adicione um bloco de negação no topo do .htaccess do web root (um exemplo de configuração defensiva). Páginas normais não são afetadas.
# === SECURITY BLOCK ===
RedirectMatch 404 (?i)/\.(env|git)(\..*)?(/|$)
<FilesMatch "(?i)\.(sql|sql\.gz|bak|old|swp|save|orig|tgz)$|^credentials\.json$|\.bk[._]">
Require all denied
</FilesMatch>
<FilesMatch "(?i)^(phpinfo|info)\.php$">
Require all denied
</FilesMatch>
# if Laravel internals are visible at /<app>/storage/... etc.
RedirectMatch 403 (?i)^/[^/]+/(storage|bootstrap/cache|config|database|resources|routes|tests|vendor)(/|$)
# === END ===Primeiros socorros não são o objetivo
Uma blacklist sempre deixa algo escapar — o composer.lock ou qualquer novo nome de arquivo. Trate-a como ganho de tempo até o Passo 2.
Passo 2: Reestruture (a correção permanente)
Mova o corpo do app para fora do web root e deixe um pequeno ponto de entrada que apenas o carrega.
# 1. Move the body outside the web root (same filesystem → mv is instant, atomic)
mv ~/example/public_html/app ~/example/app-laravel
# 2. Put only a minimal entry in the web root
mkdir -p ~/example/public_html/sub
cat > ~/example/public_html/sub/index.php <<'PHP'
<?php require __DIR__ . '/../../app-laravel/public/index.php';
PHP
# 3. Copy Laravel public/.htaccess, symlink static assets
# 4. Clear config cache
rm -f ~/example/app-laravel/bootstrap/cache/{config,services,packages,routes-v7}.phpUma lição da operação deste site: bootstrap-redirect em vez de symlink
Tornar o próprio public_html um symlink para public/ pode retornar 500 no docroot de um subdomínio cadastrado em alguns ambientes (ele não segue). Manter o docroot como um diretório real e dar require por caminho absoluto a partir de um pequeno index.php (a forma "bootstrap-redirect") é mais estável em hospedagem compartilhada com escopo restrito. Além disso, o opcache pode memorizar um carregamento falho e continuar retornando 500 — o último recurso é mudar o nome do arquivo de entrada (entry.php).
Passo 3: Rotacione as chaves (presuma que foram vistas)
Se houve qualquer janela de exposição, trate as chaves do .env como já vistas e substitua-as. Ordem: segredos de API externa / OAuth → chaves de criptografia → e-mail → banco de dados. Veja o glossário do .env. Reemitir um segredo OAuth pode invalidar os refresh_tokens, então tenha um fluxo de reautorização pronto primeiro.
Passo 4: Autoverificação (faça um hábito)
Por fim, confirme que nada está de fato exposto no seu próprio site (apenas contra domínios que você possui).
# A 200 with a body means it's exposed. 403/404 is okay for now.
curl -sI https://your-domain/.env | head -1
curl -sI https://your-domain/.git/config | head -1Verificar isto em cada deploy pega erros de posicionamento cedo.
Como verificar a sua configuração (com mais detalhe)
Além das duas linhas do Passo 4, estas verificações pegam mais do que costuma escapar. Rode-as apenas contra o seu próprio servidor e os seus próprios domínios.
Olhe o conteúdo, não só o código de status
Alguns apps respondem a qualquer URL desconhecida com a página inicial e um 200, então o status sozinho não diz nada. Imprima só o começo, por exemplo curl -s https://your-domain/.env | head -c 120, e veja se aparecem linhas de configuração como APP_KEY= ou DB_PASSWORD=. No .git/HEAD, uma linha que começa com ref: refs/heads/ significa que está exposto.
Verifique todos os apps, não só um
Repita a verificação para cada app em subdomínio ou subdiretório, não só no domínio principal. Erros de posicionamento tendem a ser copiados quando você adiciona apps, então corrigir um não significa que os outros estão corrigidos.
Procure na pasta pública do servidor
Com SSH, procure arquivos arriscados diretamente, por exemplo find ~/public_html -maxdepth 4 \( -name ".env*" -o -name ".git" -o -name "*.sql" -o -name "*.zip" \). Nenhuma saída é o resultado desejado. Só com FTP, ative "mostrar arquivos ocultos" no seu cliente FTP e revise a listagem.
Confirme que o .htaccess realmente faz efeito
Coloque na pasta pública um arquivo fictício inofensivo, sem segredos (por exemplo check.bak contendo só test), e abra-o pela URL. Você deve receber 403. Se ele abrir, o .htaccess não está sendo lido; verifique o tipo de servidor (Nginx, por exemplo) ou as configurações dele. Apague o arquivo fictício ao terminar.
Garanta que o modo de depuração está desligado
No .env, APP_ENV deve ser production e APP_DEBUG deve ser false. A documentação do Laravel diz que APP_DEBUG=true em produção arrisca expor valores de configuração sensíveis aos seus usuários. Com SSH, php artisan about --only=environment mostra o mesmo.
Erros comuns e como corrigi-los
- Apagar o .env mas deixar o .git: um
.gitexposto pode permitir reconstruir commits antigos, incluindo um.envou chaves antigas. Tire o.gitda pasta pública também e reemita qualquer segredo que já tenha sido commitado. - Deixar cópias como
.env.bakou.env.oldna pasta pública: uma cópia com valores reais é tão sensível quanto o.env. Mova-a para fora ou apague-a. Mantenha só valores de exemplo no.env.example. - Enviar o projeto inteiro como ZIP e deixá-lo lá depois de descompactar: o ZIP contém o
.env. Apague-o logo depois de descompactar. - Rodar produção com
APP_DEBUG=true: páginas de erro podem revelar valores de configuração. Em produção, deixe semprefalse. - Achar que está seguro porque escreveu o
.htaccess: Nginx e configurações comAllowOverride Nonenunca o leem. Use a verificação com arquivo fictício acima para confirmar. - Trocar o
APP_KEYde repente: segundo a documentação do Laravel, mudar a chave desconecta todos os usuários logados e torna indecifráveis os dados criptografados com a chave antiga. No Laravel 11 e posteriores, coloque a chave antiga emAPP_PREVIOUS_KEYSantes de trocar.
Um exemplo real: arquivos .env expostos coletados em grande escala
Em agosto de 2024, a equipe de pesquisa de uma empresa de segurança relatou uma operação que coletava automaticamente arquivos .env expostos em servidores web, usava as credenciais de nuvem contidas neles para copiar dados e depois exigia resgate. Segundo o relatório, cerca de 110.000 domínios foram alvo e mais de 90.000 variáveis de ambiente foram coletadas, das quais cerca de 7.000 eram credenciais de serviços de nuvem e cerca de 1.500 eram credenciais de contas de redes sociais.
O relatório atribui a causa não a uma vulnerabilidade do lado da nuvem, mas a erros de configuração das organizações vítimas, que haviam exposto seus arquivos .env por engano. Duas lições:
- Arquivos
.envexpostos não são achados por acaso; programas os procuram em grande escala - Um único
.envpode espalhar o dano para outros serviços, como armazenamento em nuvem e e-mail
Não presuma que um site pequeno não será encontrado. Um caso anonimizado do mesmo erro de posicionamento está em O .env exposto ao mundo inteiro.
Leia a seguir
- Incidente: O .env exposto ao mundo inteiro
- Glossário: O que é .env
- Fundamentos: O que há de perigoso no .env e nas chaves de API
- Quando o provedor é invadido: o que fazer quando a sua hospedagem é comprometida (o caso que as suas próprias configurações não conseguem evitar; em inglês)
- Verificar uma invasão: como verificar se o seu site ou servidor foi invadido (onde olhar se o seu .env estava público)
Fontes
- Documentação do Laravel, "Deployment" (aponte as requisições para
public/index.php, nunca sirva a partir da raiz do projeto,APP_DEBUG=falseem produção): laravel.com - Documentação do Laravel, "Configuration" (não commite o
.envno controle de versão; Debug Mode): laravel.com - Documentação do Laravel, "Encryption" (rotação do
APP_KEYeAPP_PREVIOUS_KEYS): laravel.com - Apache HTTP Server 2.4, "core - AllowOverride" (o
.htaccessnão é lido quando está comoNone; o padrão éNone): httpd.apache.org - Apache HTTP Server 2.4, ".htaccess files" (quando usar e ressalvas): httpd.apache.org
- OWASP, "WSTG-CONF-04 Review Old Backup and Unreferenced Files for Sensitive Information": owasp.org
- Unit 42 (Palo Alto Networks), "Leaked Environment Variables Allow Large-Scale Extortion Operation in Cloud Environments" (15 de agosto de 2024): unit42.paloaltonetworks.com