Antes do deploy: 30 verificações de segurança que faço em uma aplicação PHP
Checklist prático de pré-deploy para PHP: 30 verificações de código, dependências, autenticação, dados, configuração e infraestrutura antes de liberar para produção.

Colocar uma aplicação em produção não deveria significar apenas executar git pull, instalar as dependências e reiniciar o PHP-FPM.
Antes de liberar uma aplicação para a internet, eu gosto de fazer uma última revisão de segurança.
Não estou falando de substituir um pentest, uma auditoria de código ou uma análise completa baseada no OWASP ASVS. A ideia aqui é outra: ter um checklist prático de pré-deploy capaz de encontrar problemas comuns antes que eles cheguem à produção.
O OWASP ASVS, por exemplo, fornece uma base para verificar controles técnicos de segurança de aplicações web. A versão estável atual é a 5.0.0.
Na prática, meu checklist é dividido em código, dependências, autenticação, dados, arquivos, configuração do PHP e infraestrutura.
1. Estou usando uma versão do PHP suportada?
A primeira verificação é simples:
php -vNão gosto de colocar uma aplicação nova em produção usando uma versão do PHP que já está próxima do fim de vida.
O ciclo oficial do PHP prevê dois anos de suporte ativo e mais dois anos de suporte apenas para questões críticas de segurança.
No momento em que este artigo foi escrito, por exemplo, o PHP 8.2 ainda recebe correções de segurança até 31 de dezembro de 2026, enquanto o PHP 8.3 vai até 31 de dezembro de 2027, o PHP 8.4 até 31 de dezembro de 2028 e o PHP 8.5 até 31 de dezembro de 2029.
Não basta perguntar “está funcionando?”. A pergunta deveria ser:
“Essa versão continuará recebendo correções de segurança durante a vida esperada da aplicação?”
2. Rodei o composer audit
Antes do deploy:
composer auditO Composer possui um mecanismo próprio de auditoria que consulta avisos de segurança de dependências e pode detectar pacotes vulneráveis, abandonados ou sinalizados por políticas de dependência.
Também posso usar:
composer audit --lockedpara auditar o conteúdo definido pelo composer.lock.
Esse comando já deveria fazer parte do pipeline de CI/CD.
Uma vulnerabilidade em uma biblioteca pode ser tão perigosa quanto uma vulnerabilidade no meu próprio código.
3. O composer.lock está versionado?
Em aplicações, especialmente aquelas que chegam à produção, eu verifico se o composer.lock está presente e atualizado.
Exemplo:
git status
git diff composer.lockO objetivo é garantir que desenvolvimento, CI e produção não estejam instalando versões diferentes das dependências.
Também verifico:
composer validate --strictNão quero descobrir durante o deploy que o ambiente de produção está resolvendo um conjunto diferente de pacotes.
4. Removi dependências de desenvolvimento da produção?
Produção não precisa carregar tudo que foi usado para desenvolver e testar a aplicação.
Por exemplo:
composer install --no-dev --optimize-autoloaderAlém de reduzir o tamanho da instalação, isso diminui a superfície exposta pela aplicação.
Ferramentas de debug, profiling, testes e utilitários internos não precisam estar disponíveis em um servidor de produção.
O próprio Composer permite usar --no-dev para não instalar as dependências de desenvolvimento.
5. Não existem secrets no Git
Faço uma busca por coisas como:
password=
secret=
api_key=
token=
AWS_SECRET_ACCESS_KEY=
DATABASE_URL=
PRIVATE_KEYTambém verifico:
git log --all -- .enve:
git ls-files | grep -E '(^|/)\.env'Uma variável de ambiente evita colocar o segredo diretamente no código, mas ela não resolve tudo sozinha.
O importante é garantir que:
- o segredo não esteja no Git;
- não apareça em logs;
- não seja impresso em exceções;
- não seja incorporado na imagem Docker;
- não seja exposto no frontend;
- possua permissões mínimas;
- possa ser rotacionado.
O OWASP recomenda tratar credenciais, API keys, certificados e outras informações desse tipo como secrets que precisam de armazenamento, controle de acesso, auditoria e rotação adequados.
6. O .env de produção está realmente protegido?
Não adianta tirar o .env do Git e depois deixá-lo acessível pela web.
Verifico:
ls -lae, dependendo do ambiente:
chmod 600 .envTambém verifico se o servidor web não permite acessar arquivos de configuração, backups, arquivos temporários ou outros artefatos do projeto.
A aplicação precisa expor apenas aquilo que realmente deveria ser público.
7. APP_DEBUG está desativado?
Em aplicações como Symfony e Laravel, procuro explicitamente por configurações de produção.
Por exemplo:
APP_ENV=prod
APP_DEBUG=0O que eu não quero ver em produção:
stack trace
caminho absoluto do servidor
nome de arquivo interno
SQL gerado
variáveis de ambiente
credenciais
informações sobre serviços internosUma exceção detalhada é extremamente útil no desenvolvimento e extremamente útil para um atacante em produção.
8. O PHP não está expondo informações desnecessárias?
Verifico principalmente:
display_errors = Off
display_startup_errors = Off
log_errors = OnTambém verifico informações expostas pelo runtime.
Por exemplo, o expose_php pode fazer o servidor informar que está utilizando PHP por meio do header X-Powered-By. A própria documentação do PHP explica esse comportamento.
Não considero ocultar versão uma proteção principal, mas reduzir exposição desnecessária faz parte de uma configuração mais limpa.
9. O acesso ao banco usa queries parametrizadas?
Procuro por códigos como:
$sql = "SELECT * FROM users WHERE email = '$email'";Isso é um sinal de alerta.
Prefiro:
$stmt = $pdo->prepare(
'SELECT * FROM users WHERE email = :email'
);
$stmt->execute([
'email' => $email,
]);Ou os mecanismos equivalentes do ORM/framework.
A orientação do OWASP é evitar concatenar entrada controlada pelo usuário diretamente em SQL e utilizar queries parametrizadas.
Faço essa busca não apenas em SELECT, mas também em:
INSERT
UPDATE
DELETE
ORDER BY
LIKE
LIMIT
filtros
relatórios
queries administrativas10. Toda entrada é validada no servidor?
Não confio apenas em:
required
maxlength
type="email"
pattern="..."Validação no frontend melhora a experiência do usuário, mas não é uma barreira de segurança.
O backend deve validar os dados novamente.
Por exemplo:
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('E-mail inválido.');
}O OWASP recomenda validação no lado do servidor e, quando possível, abordagem baseada em allowlist.
11. Existe proteção contra XSS?
Procuro principalmente por dados do usuário sendo enviados diretamente para HTML.
Exemplo perigoso:
echo $name;Dependendo do contexto, o correto pode ser:
echo htmlspecialchars(
$name,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);Frameworks e templates modernos normalmente possuem mecanismos de escaping, mas eu verifico os casos em que o desenvolvedor desativou esse comportamento.
Também reviso:
- HTML armazenado;
- conteúdo rico;
- parâmetros de URL;
- mensagens de erro;
- valores inseridos em JavaScript;
- atributos HTML.
Output encoding precisa considerar o contexto em que o dado será utilizado.
12. Os formulários que alteram estado possuem proteção contra CSRF?
Principalmente aplicações que utilizam autenticação baseada em cookies.
Eu verifico:
criação
edição
exclusão
alteração de senha
alteração de e-mail
operações administrativas
pagamentosO ideal é utilizar a proteção CSRF fornecida pelo framework quando ela existir.
O OWASP recomenda tokens CSRF para requisições que alteram estado em aplicações baseadas em cookies e reforça que operações de alteração não deveriam utilizar GET.
Por exemplo, isto é uma péssima ideia:
GET /usuario/123/deletePrefiro algo como:
DELETE /usuarios/123ou um POST protegido por CSRF, dependendo da arquitetura.
13. Sessões estão configuradas corretamente?
Sessão é um dos pontos que mais facilmente passa despercebido.
No PHP, verifico principalmente configurações como:
session.use_cookies = On
session.use_only_cookies = On
session.use_strict_mode = On
session.use_trans_sid = OffO manual do PHP recomenda atenção especial a essas configurações, incluindo session.use_strict_mode, que impede a utilização de IDs de sessão não criados pelo próprio módulo de sessão.
Também verifico se o cookie de sessão possui:
Secure
HttpOnly
SameSiteO objetivo é reduzir as possibilidades de roubo ou abuso do identificador da sessão.
14. O ID de sessão é renovado após autenticação?
Depois do login, não quero simplesmente manter a mesma sessão.
Procuro por algo equivalente a:
session_regenerate_id(true);Isso ajuda a reduzir o risco de session fixation.
Também verifico operações como:
login
logout
troca de senha
elevação de privilégio
troca de usuárioO OWASP recomenda tratar o identificador de sessão como dado não confiável e utilizar gerenciamento estrito de IDs.
15. Senhas estão sendo armazenadas com hashing adequado?
Se eu encontro:
md5($password)ou:
sha1($password)para armazenar senha, o problema é imediato.
Senhas não devem ser armazenadas em texto puro nem com hashes rápidos como MD5 ou SHA-256.
Prefiro:
$passwordHash = password_hash(
$password,
PASSWORD_ARGON2ID
);e:
password_verify($password, $passwordHash);O OWASP recomenda algoritmos adaptativos como Argon2id, bcrypt ou PBKDF2 para armazenamento de senhas.
16. Existe controle contra brute force?
Principalmente em:
/login
/reset-password
/forgot-password
/verify-code
/2faEu verifico:
rate limiting
bloqueio temporário
controle por IP
controle por conta
captcha quando necessário
logs
alertasUm login protegido apenas por usuário e senha pode continuar vulnerável mesmo que a senha esteja armazenada corretamente.
Segurança não termina no password_verify().
17. A autorização acontece no backend?
Uma das minhas verificações favoritas é perguntar:
“Se eu remover o botão da tela, a operação continua bloqueada?”
Se a resposta for não, existe um problema.
Exemplo:
GET /admin/users/10não deveria ser acessível simplesmente porque o usuário conseguiu descobrir a URL.
O backend precisa verificar:
usuário autenticado?
possui permissão?
é dono do recurso?
pode executar esta ação?Isso é especialmente importante em APIs e casos de IDOR/BOLA.
18. IDs previsíveis estão protegidos por autorização?
Trocar:
/cliente/100por:
/cliente/101não pode ser suficiente para acessar outro registro.
UUIDs podem dificultar enumeração, mas não substituem autorização.
A regra continua sendo:
identificação != autorizaçãoMesmo que eu use UUID:
/cliente/550e8400-e29b-41d4-a716-446655440000eu ainda preciso validar se aquele usuário pode acessar o recurso.
19. Uploads estão realmente protegidos?
Upload de arquivo merece uma revisão específica.
Eu verifico:
extensão permitida
MIME type
conteúdo real
tamanho máximo
nome do arquivo
diretório de armazenamento
permissão
autorizaçãoNunca confio apenas nisso:
$_FILES['file']['type']O Content-Type enviado pelo cliente pode ser manipulado.
O OWASP recomenda allowlist de extensões, validação baseada no conteúdo, limite de tamanho, nomes gerados pela aplicação e armazenamento fora do webroot quando possível.
Também verifico se um upload de:
.php
.phtml
.phar
.htaccessou outros formatos executáveis pode acabar em um diretório servido pelo servidor web.
20. Existe proteção contra Path Traversal?
Procuro qualquer código que faça:
$file = $_GET['file'];
readfile($file);ou:
include $_GET['page'];ou:
$path = $basePath . '/' . $_GET['filename'];Entradas que influenciam caminhos de arquivos precisam ser tratadas com muita cautela.
Eu verifico especialmente parâmetros como:
file
path
filename
template
document
download
attachment
image21. Usuários conseguem controlar comandos do sistema?
Outro ponto que procuro cuidadosamente:
exec($command);
shell_exec($command);
system($command);
passthru($command);E principalmente casos como:
shell_exec("convert " . $filename);Mesmo quando existe necessidade real de executar um processo externo, a entrada precisa ser estritamente controlada e o comando deve ser construído de forma segura.
Eu trato qualquer dado vindo do usuário que chegue próximo de:
shell
CLI
exec
bash
imagemagick
ffmpeg
git
zip
unzipcomo uma área que merece revisão manual.
22. Existe risco de SSRF?
Se a aplicação recebe uma URL do usuário e depois faz:
$response = file_get_contents($url);eu paro para investigar.
Casos comuns:
webhooks
importação de imagens
integrações
consulta de URLs
geradores de PDF
proxy
preview de links
callbacksSSRF pode fazer a aplicação acessar recursos internos que não deveriam estar disponíveis para o usuário.
O OWASP recomenda aplicar validação adequada e, quando possível, allowlists de destinos confiáveis. Também recomenda atenção a redirecionamentos utilizados para contornar validações.
23. CORS está restritivo?
Procuro por algo como:
Access-Control-Allow-Origin: *e pergunto:
“A API realmente precisa ser acessível por qualquer origem?”
Se a resposta for não, especifico explicitamente os domínios permitidos.
Por exemplo:
https://app.exemplo.com
https://admin.exemplo.comem vez de liberar tudo.
O OWASP recomenda especificar origens quando CORS é necessário, em vez de simplesmente usar *.
24. A aplicação está sempre atrás de HTTPS?
Em produção:
HTTP → HTTPSEu verifico:
curl -I http://exemplo.com
curl -I https://exemplo.comQuero confirmar:
- certificado válido;
- redirecionamento correto;
- cookies Secure;
- ausência de conteúdo sensível via HTTP;
- configuração correta do proxy reverso.
Também considero HSTS quando a aplicação e a operação do domínio estão preparadas para isso.
25. Os headers de segurança estão configurados?
Faço uma inspeção:
curl -I https://exemplo.comE verifico headers como:
Content-Security-Policy
Strict-Transport-Security
X-Content-Type-Options
Content-Security-Policy: frame-ancestors ...
Referrer-Policy
Permissions-PolicyA configuração depende do tipo de aplicação, mas headers HTTP podem adicionar uma camada importante de defesa contra XSS, clickjacking, MIME sniffing e exposição indevida.
Não gosto de copiar um conjunto de headers cegamente.
Eles precisam fazer sentido para a aplicação.
26. Logs não estão vazando informações sensíveis?
Procuro por:
password
token
authorization
cookie
secret
credit_card
cpf
cnpj
api_keyEspecialmente em:
logger->debug($request);ou:
Log::info($request->all());Logs precisam ajudar na investigação de incidentes sem se transformar em um banco de dados de credenciais.
Também verifico quem possui acesso aos logs e por quanto tempo eles são armazenados.
27. Erros estão indo para o log, mas não para o usuário?
O comportamento ideal geralmente é:
usuário → mensagem genérica
servidor → erro detalhado no logPor exemplo:
Não foi possível processar a solicitação.e não:
PDOException:
SQLSTATE[HY000]
/var/www/app/src/Repository/UserRepository.php:143A recomendação do OWASP para revisão de código inclui garantir que mensagens de erro não revelem informações sensíveis.
28. Backup está protegido?
Segurança não termina na aplicação.
Verifico:
backup do banco
backup dos uploads
backup dos arquivos de configuração
permissões
criptografia
retenção
acesso
restauraçãoPrincipalmente:
“Meu backup contém as mesmas credenciais e dados sensíveis da produção?”
E outra pergunta importante:
“Eu já testei restaurar esse backup?”
Backup que nunca foi restaurado é apenas uma expectativa.
29. O servidor está expondo serviços que não deveria?
Antes do deploy, eu verifico portas abertas:
ss -lntupou, dependendo do ambiente:
sudo ss -lntupQuero identificar se serviços como:
MySQL
PostgreSQL
Redis
RabbitMQ
MongoDB
php-fpm
adminer
portainerestão acessíveis pela internet sem necessidade.
A arquitetura ideal é que serviços internos permaneçam internos.
Por exemplo:
Internet
|
v
Nginx/Apache
|
v
PHP-FPM
|
+----> PostgreSQL
|
+----> Redise não:
Internet
|
+----> PostgreSQL
+----> Redis
+----> RabbitMQ30. Eu fiz pelo menos um teste manual como um atacante?
Essa é a verificação final.
Depois de todo o checklist, eu tento quebrar minha própria aplicação.
Não precisa começar com ferramentas complexas.
Eu tento coisas simples:
acessar uma URL administrativa sem login
trocar o ID de um recurso
enviar campos extras
enviar valores vazios
enviar strings enormes
alterar métodos HTTP
repetir requisições rapidamente
usar HTML nos campos
enviar arquivos inesperados
alterar headers
remover tokens CSRF
acessar endpoints diretamente
usar uma sessão antiga depois do logoutTambém verifico APIs com ferramentas como:
curl
Postman
Burp Suite
OWASP ZAPA ideia é sair do pensamento:
“O fluxo normal funciona.”
e testar:
“O que acontece quando alguém faz algo que eu não esperava?”
Meu checklist de pré-deploy
No dia a dia, eu gosto de reduzir tudo isso a uma lista que possa ser marcada:
[ ] 01. PHP em versão suportada
[ ] 02. composer audit executado
[ ] 03. composer.lock versionado e atualizado
[ ] 04. Dependências de desenvolvimento removidas
[ ] 05. Secrets fora do Git
[ ] 06. .env/configuração protegidos
[ ] 07. Debug desativado
[ ] 08. Informações desnecessárias do PHP ocultadas
[ ] 09. Queries parametrizadas
[ ] 10. Input validado no backend
[ ] 11. Proteção contra XSS
[ ] 12. CSRF configurado
[ ] 13. Sessões protegidas
[ ] 14. Regeneração do ID de sessão
[ ] 15. Senhas armazenadas com hashing adequado
[ ] 16. Rate limiting/brute force revisado
[ ] 17. Autorização no backend
[ ] 18. Proteção contra IDOR/BOLA
[ ] 19. Uploads protegidos
[ ] 20. Path Traversal revisado
[ ] 21. Execução de comandos revisada
[ ] 22. SSRF revisado
[ ] 23. CORS restritivo
[ ] 24. HTTPS obrigatório
[ ] 25. Security headers revisados
[ ] 26. Logs sem secrets
[ ] 27. Erros sem informações sensíveis
[ ] 28. Backups protegidos e testados
[ ] 29. Serviços internos não expostos
[ ] 30. Teste manual de segurança executadoSegurança não é uma etapa depois do desenvolvimento
Uma coisa que aprendi trabalhando com aplicações PHP é que segurança dificilmente é resolvida adicionando uma única biblioteca no projeto.
Ela aparece em pequenas decisões:
como o SQL é montado;
como o arquivo é salvo;
como a sessão é criada;
como o usuário é autorizado;
como o erro é registrado;
como o segredo é armazenado;
como o servidor está exposto.Uma aplicação pode estar usando PHP moderno, framework atualizado e dependências recentes e ainda assim possuir uma vulnerabilidade grave criada por poucas linhas de código.
Por isso, antes de cada deploy, eu prefiro gastar alguns minutos perguntando:
“O que eu estou permitindo que um usuário malicioso faça com esta aplicação?”
Essa pergunta costuma encontrar problemas que os testes funcionais não encontram.
E este checklist não é um substituto para uma análise de segurança completa. Para aplicações mais críticas, eu ainda considero revisão de código, testes automatizados de segurança, análise de infraestrutura, SAST/DAST e uma avaliação baseada em referências como o OWASP ASVS.
No final, o objetivo não é tornar a aplicação “impossível de atacar”.
Isso não existe.
O objetivo é evitar que um problema básico chegue à produção simplesmente porque ninguém parou para fazer a pergunta certa antes do deploy.
Referências
- OWASP Application Security Verification Standard (ASVS).
- OWASP Cheat Sheet Series — Secure Code Review.
- OWASP Cheat Sheet Series — Secrets Management.
- OWASP Cheat Sheet Series — Session Management.
- OWASP Cheat Sheet Series — SQL Injection Prevention.
- OWASP Cheat Sheet Series — File Upload.
- OWASP Cheat Sheet Series — HTTP Security Headers.
- PHP Manual — Session Security.
- PHP — Supported Versions.
- Composer —
audit.