Pular para o conteúdo
>_ITDITDPlataforma de Segurança Web

Guias de Segurança

O .env de apps Laravel era legível pelo mundo inteiro — o erro mais comum de hospedagem compartilhada

Em hospedagem compartilhada, apps Laravel acabam com .env, .git, dumps de DB e segredos OAuth legíveis por HTTP. A causa: o projeto inteiro sob a raiz web em vez de só o public/. A correção em três passos e por que é um padrão ruim do setor inteiro.

Publicado 2026-06-07 7 min de leitura

Uma configuração incorreta que realmente acontece em hospedagem compartilhada, com domínios e valores removidos e transformada em lição. Isto não é sobre encontrar os sites de outras pessoas — é sobre proteger seu próprio deploy (ou um herdado).

Ficha do caso — INCIDENT FILE
Classe
Exposição de .env / .git / dumps de DB (configuração incorreta)
Severidade
Crítica (todo segredo legível por HTTP)
Causa
Corpo do Laravel colocado diretamente sob o public_html (só o public/ deveria ser exposto)
Propagação
Cada novo app copiava o mesmo buraco → dezenas deles
Correção permanente
Corpo fora da docroot + só o public/ com symlink

Os piores arquivos são .env e .git

O .env é um chaveiro: autenticação de DB, e-mail, APIs externas, a chave de criptografia do app (APP_KEY). Com o .git público, invasores recuperam não só os valores atuais, mas cada rotação passada percorrendo o histórico do git. Veja O que é o .env.

O que estava acontecendo

No host compartilhado, muitos apps Laravel estavam dispostos assim. Eis os layouts perigoso e correto lado a lado:

Layout perigoso (comum)

~/public_html/
└── app/          ← Laravel inteiro (errado)
    ├── .env      ← legível em /app/.env
    ├── .git/     ← clone restaura o histórico completo
    ├── vendor/  config/  storage/
    └── public/   ← a ÚNICA coisa que deveria estar aqui

Layout correto

~/laravel/app/    ← corpo fora da raiz web
├── .env  .git/   ← inacessível por HTTP = seguro
└── public/       ← só este, exposto via symlink
        └── index.php

Arquivos típicos que retornavam 200 OK para o mundo externo:

ArquivoPor que é perigoso
.env / .env.bk_*autenticação de DB, e-mail, chaves de API externas, APP_KEY — tudo em texto puro
o diretório .git/git clone restaura o código-fonte e o histórico completo
composer.lock / package-lock.jsonversões exatas de dependências = uma lista de alvos com CVE conhecido
credentials.jsonsegredos de cliente OAuth, empacotados
*.sql / db-*.sql.gzum dump completo do banco de dados, dezenas de MB compactados

A parte assustadora: a propagação

Uma vez disposto assim, cada novo app do mesmo tipo copia o mesmo buraco. Uma configuração incorreta vira dezenas. A regra: no momento em que você notar, inspecione todos.

A correção em três passos

Passo 1 — Primeiros socorros: bloqueie arquivos sensíveis no nível do HTTP

Primeiro, pare "mais vazamento". É reversível e não afeta as páginas normais. Adicione um bloco de deny no topo do public_html/.htaccess (um exemplo de configuração defensiva):

# === SECURITY BLOCK ===
# 404 anything under .env / .git
RedirectMatch 404 (?i)/\.(env|git)(\..*)?(/|$)
 
# deny backups, dumps, credential files
<FilesMatch "(?i)\.(sql|sql\.gz|bak|old|swp|save|orig|tgz)$|^credentials\.json$|\.bk[._]">
    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 ===

Mas uma blacklist é fundamentalmente enxugar gelo. Você vai esquecer o composer.lock; novos nomes de arquivo adicionam novos buracos. Trate-a como primeiros socorros de melhor esforço e coloque a correção real no Passo 3.

Passo 2 — Rotacione as chaves (suponha que foram vistas)

Mesmo depois de bloquear com o .htaccess, trate os segredos como já vistos. Em ordem de prioridade:

  1. Chaves de API externas (prioridade máxima, imediato): elas mapeiam para cobrança e tomada de conta. Revogue + reemita o CLIENT_SECRET OAuth, chaves de API e credenciais de nuvem, com escopo de menor privilégio.
  2. Segredos de cliente OAuth.
  3. APP_KEY: regenerar pode quebrar sessões e tornar colunas de DB criptografadas indecifráveis — verifique o raio de explosão primeiro.
  4. E-mail/SMTP, credenciais de DB.

Uma armadilha de rotação (de fato encontrada em campo)

Com alguns provedores OAuth, reemitir o segredo de cliente também invalida os refresh_tokens existentes — a renovação com tokens armazenados falha e causa uma queda em produção. Sempre tenha um fluxo de reautorização pronto antes de reemitir. E atualize seu .env / config local também, ou o próximo deploy reintroduz o valor antigo.

Passo 3 — Reestruture: mova o corpo para fora da docroot (a correção real)

Redisponha cada app assim:

# 1. Mova o corpo para fora da raiz web (mesmo sistema de arquivos → mv é instantâneo, atômico)
mv ~/example/public_html/app  ~/example/app-laravel
 
# 2. Coloque só um bootstrap mínimo na raiz web
mkdir -p ~/example/public_html/sub
cat > ~/example/public_html/sub/index.php <<'PHP'
<?php require __DIR__ . '/../../app-laravel/public/index.php';
PHP
# 3. Copie o public/.htaccess do Laravel, faça symlink dos assets estáticos, limpe o cache de config

Nesta forma, .env, .git e vendor/ simplesmente não existem na raiz web, então você fica seguro sem depender de regras do .htaccess. Os passos completos estão em Mantendo o .env fora da web pública em hospedagem compartilhada.

✗ Perigoso: corpo dentro da raiz web

— fronteira web (legível por HTTP) —

.env · .git · vendor · public

↑ tudo dentro da linha = totalmente exposto

✓ Seguro: corpo fora da raiz web

— fronteira web (legível por HTTP) —

só public/ (symlink)

.env · .git estão fora = inacessíveis

Uma linha decide — dentro ou fora da raiz web. Se .env e .git ficam dentro dela, qualquer um pode lê-los por HTTP.

Armadilhas "que não estão no manual"

  • Problema de escopo de subdomínio: repontar a docroot de um subdomínio registrado para outra árvore via symlink pode não seguir e retornar 500. → Mantenha a docroot um diretório real e faça require por caminho absoluto a partir de um pequeno index.php dentro dele (a forma "bootstrap-redirect") para estabilidade.
  • opcache lembra de um estado quebrado: o opcache pode memorizar uma falha de carga do index.php e seguir retornando 500 mesmo depois de você reconstruir. Último recurso: troque o nome do arquivo (entry.php, etc.).
  • Uma docroot de subdomínio "não registrado" legível pelo pai: https://sub.example/ pode ser inválido enquanto https://example/sub/ ainda mostra o conteúdo — uma entrada facilmente esquecida. Ao inspecionar, bloqueie também o caminho do domínio-pai.
  • Segredos hardcoded em config/*.php: combinados com um .git público, o histórico revela cada rotação. Sempre passe por env() e nunca coloque valores reais em defaults de fallback.

A maior lição: isto não é "o erro de uma pessoa"

A cultura inicial de hospedagem compartilhada colocava tudo diretamente sob o public_html. O Laravel (que espera só o public/ exposto) entrou nisso, e um layout perigoso de "projeto sob public_html/<app>/, expor seu public/" virou o padrão de fato — a própria documentação dos fornecedores usa exemplos baseados em public_html, o que piora. Então corrija com processo, não vigilância:

Previna a recorrência com processo

  • Faça o padrão de deploy ser "corpo fora da docroot, só o public/ com symlink".
  • Verificações de CI/lint: nunca comitar .env; nunca colocar um projeto diretamente sob public_html/<app>.
  • Autoverificação após deploy: confirme que /.env e /.git/config não podem ser obtidos de fora.

Este site incorpora esse hábito de "inspecione seu próprio site você mesmo" em sua trilha de aprendizado. → O que é perigoso em .env e chaves de API

Leia a seguir

FAQ

QMeu .env está exposto — o que faço primeiro?
A

Estanque o sangramento (bloqueie o caminho via .htaccess), depois rotacione as chaves de API externas e segredos OAuth no .env em ordem de prioridade (suponha que foram vistos), depois mova o corpo do app para fora da raiz web para ficar seguro sem depender de regras.

QPor que colocar o Laravel sob o public_html é perigoso?
A

O Laravel foi feito para expor só o public/. Coloque o projeto inteiro na docroot e tudo acima dele — .env (todos os seus segredos), .git (histórico recuperável), vendor/ — fica legível por HTTP.

QBloquear com uma blacklist no .htaccess é suficiente?
A

Funciona como primeiros socorros, mas é fundamentalmente enxugar gelo — cada novo nome de arquivo adiciona um buraco. A correção real é a mudança estrutural: coloque o corpo do app fora da docroot e exponha só o public/ via symlink.