Boas Práticas de Segurança Web: Guia Camada por Camada (2026)

Advertisement
O Que São Boas Práticas de Segurança Web
Boas práticas de segurança web são o conjunto de controles que impedem que um site ou uma aplicação web seja invadido, desfigurado, usado para atacar os próprios visitantes ou drenado de dados em silêncio. Não se trata de um único produto nem de uma única configuração. É uma pilha de decisões que começa no registrador do domínio e passa pelo DNS, pelo TLS, pelos cabeçalhos HTTP que o seu servidor envia, pelo código que cuida de logins e entradas de dados, pelos pacotes de terceiros sobre os quais você constrói, pelo próprio servidor e, por fim, pelos logs que avisam quando algo deu errado.
O motivo para pensar em camadas é que os atacantes pensam assim. O Data Breach Investigations Report 2026 da Verizon constatou que 31% das violações agora começam com a exploração de uma vulnerabilidade de software, o que pela primeira vez superou as credenciais roubadas como a porta de entrada mais comum, e que 48% de todas as violações envolvem ransomware. Basta um patch ausente, uma chave de API vazada ou uma página de administração sem rate limiting. Nenhum dos controles deste guia é exótico; os sites invadidos costumam ser os que pularam o básico em uma camada enquanto poliam outra.
Este guia vai de fora para dentro. Cada prática explica por que ela importa, o que configurar exatamente e como verificar, com um comando ou uma ferramenta grátis, porque a falha mais comum que vemos nas verificações de cabeçalhos HTTP e nas verificações de SSL não é uma decisão ruim, mas uma configuração que alguém achava que estava ativa e nunca confirmou.
A Pilha de Segurança Web: Seis Camadas para Proteger
Todo ataque a um site atinge uma de seis camadas. A tabela abaixo é o mapa do restante deste guia: o que fica em cada camada, como ela costuma ser atacada e qual verificação grátis confirma que a sua defesa está realmente funcionando.
| Camada | O que os atacantes fazem aqui | Práticas essenciais | Verifique com |
|---|---|---|---|
| 1. Domínio e DNS | Sequestram o domínio, trocam os nameservers, assumem subdomínios órfãos, obtêm certificados fraudulentos | Registrar lock, MFA, DNSSEC, CAA, auditoria de registros antigos | Consulta WHOIS, Consulta DNS, Localizador de Subdomínios |
| 2. Transporte (TLS) | Rebaixam a conexão para HTTP, removem a criptografia, exploram cifras antigas, certificados expirados | TLS 1.2+, HSTS com preload, renovação automatizada | Verificador de SSL |
| 3. Cabeçalhos HTTP | Injetam scripts (XSS), colocam o site em frames para clickjacking, farejam tipos de conteúdo, vazam o referrer | CSP, frame-ancestors, nosniff, Referrer-Policy, Permissions-Policy | Verificador de Cabeçalhos HTTP |
| 4. Aplicação | Credential stuffing, injeção, controle de acesso quebrado, CSRF, uploads inseguros | Argon2id + MFA + rate limits, consultas parametrizadas, autorização no servidor, cookies SameSite | Revisão de código, OWASP ZAP, Testador de Força de Senha |
| 5. Dependências | Pacotes envenenados, CVEs conhecidas em bibliotecas, pipelines de CI comprometidos | Lockfiles, auditorias, versões fixadas, SBOM, tokens com privilégio mínimo | npm audit, Dependabot, Trivy |
| 6. Servidor e operações | Portas abertas, credenciais padrão, patches ausentes, nenhum backup, nenhum log | Firewall, SSH só com chave, aplicação de patches, backups 3-2-1, logs centralizados | Verificador de Portas, Verificador de Blacklist de IP, Saúde do Domínio |
Advertisement
Camada 1: Blinde o Domínio e o DNS
Todo o resto deste guia pressupõe que você ainda controla o seu domínio. Se um atacante conseguir entrar no seu registrador ou trocar os seus nameservers, ele pode apontar o seu tráfego para qualquer lugar, obter um certificado válido para o seu nome e ler todos os e-mails enviados a você, sem que nenhuma linha do código da sua aplicação chegue a rodar. Por isso, a segurança do domínio e do DNS é a primeira das boas práticas de segurança web, e ela é quase toda feita de configuração.
Comece com uma consulta WHOIS do seu próprio domínio e confirme três coisas: os códigos de status incluem clientTransferProhibited, a data de expiração está a mais de um ano de distância e o registrador é um em que você de fato tem conta. É surpreendentemente comum o domínio de uma empresa estar parado na conta de registrador de um ex-prestador de serviços. Nosso guia de consulta WHOIS explica cada código de status que você vai encontrar.
Registrar Lock, MFA e Alertas de Expiração
Ative o registrar lock (também chamado de bloqueio de transferência) para que o domínio não possa ser levado para outro registrador sem que você o desbloqueie explicitamente, e ative a autenticação multifator na própria conta do registrador. Contas de registrador são alvo preferido de phishing justamente porque um único login controla tudo o que vem depois. Use uma chave de segurança física ou um app autenticador em vez de SMS sempre que o registrador permitir.
Deixe o domínio em renovação automática com uma forma de pagamento que não vá expirar e, mesmo assim, crie um alerta na agenda 60 dias antes da data de expiração. Domínios expirados são capturados por drop-catchers em poucas horas, e o comprador herda o seu e-mail, o seu tráfego e todos os logins OAuth que confiam no seu domínio.
O registry lock (um bloqueio manual, fora de banda, no nível do registro) vale a taxa extra para um domínio do qual uma empresa depende; ele impede que até uma conta de registrador comprometida troque os nameservers.
Separe as funções. Quem paga pelo domínio, a conta que é dona dele e o provedor de DNS devem estar documentados e não podem depender da caixa de e-mail de um único funcionário.
Ative a privacidade do WHOIS para que os seus dados de contato não virem um mapa para phishing, e mantenha na conta um endereço de função monitorado, como
domains@.
Assine a Zona com DNSSEC
O DNSSEC assina a sua zona DNS para que um resolvedor consiga detectar respostas falsificadas. Sem ele, um atacante capaz de envenenar o cache de um resolvedor pode mandar os seus visitantes para um servidor falso enquanto o navegador continua exibindo o seu nome de domínio. A maioria dos provedores de DNS gerenciado (Cloudflare, Route 53, Google Cloud DNS e muitos registradores) o ativa com um clique; a única etapa manual é publicar o registro DS no seu registrador. Verifique com dig ou com a checagem de DNSSEC da ferramenta Saúde do Domínio:
dig +dnssec +short yourdomain.com A
# A signed zone returns the record AND an RRSIG line:
# 203.0.113.10
# A 13 2 3600 20261015000000 20260924000000 34505 yourdomain.com. Kx3f...==
dig +short yourdomain.com DS
# A DS record at the parent zone proves the chain of trust is complete
# 34505 13 2 6A1B...C9Restrinja a Emissão de Certificados com CAA
Um registro CAA (RFC 8659) informa às autoridades certificadoras quais delas podem emitir certificados para o seu domínio. Toda CA pública é obrigada a consultá-lo antes de emitir, então um registro CAA fecha a porta para um atacante que tenha enganado a validação de domínio de alguma outra CA. Três registros cobrem os casos comuns: quem pode emitir, quem pode emitir certificados wildcard e para onde reportar uma solicitação recusada:
yourdomain.com. CAA 0 issue "letsencrypt.org"
yourdomain.com. CAA 0 issuewild ";"
yourdomain.com. CAA 0 iodef "mailto:security@yourdomain.com"Audite Registros Antigos: Subdomain Takeover
Um subdomain takeover (sequestro de subdomínio) acontece quando um registro DNS ainda aponta para um serviço que você deixou de usar: um CNAME para um site do GitHub Pages excluído, um app do Azure, um bucket S3 ou o hostname de um fornecedor SaaS. Qualquer pessoa pode registrar o destino abandonado e passar a servir conteúdo em old-app.yourdomain.com em seu nome, com direito a certificado válido. É uma das descobertas mais comuns em programas de bug bounty, porque ninguém lembra que o registro existe.
Passe o seu domínio pelo Localizador de Subdomínios, que lê os logs de certificate transparency, para ver todos os hostnames para os quais um certificado já foi emitido. Depois, resolva cada um com uma consulta DNS e exclua qualquer registro cujo destino retorne NXDOMAIN ou a página "no such app" de um provedor. Repita a cada trimestre e inclua a exclusão do registro DNS no processo de desativação de qualquer serviço.
Camada 2: HTTPS e TLS Bem Configurados
HTTPS deixou de ser uma boa prática; é o mínimo, e os navegadores marcam páginas em HTTP puro como "Não seguro". O que ainda separa um site seguro de um site apenas criptografado são as versões de TLS que você aceita, se o HTTP ainda pode ser usado para chegar até você e se a renovação está automatizada o suficiente para sobreviver aos cortes na validade dos certificados que começaram em março de 2026.
Advertisement
TLS 1.2 no Mínimo, TLS 1.3 de Preferência
O TLS 1.0 e o 1.1 foram oficialmente descontinuados pela RFC 8996 em 2021, e nenhum navegador atual os negocia. Desative-os no servidor, dê preferência ao TLS 1.3 (que elimina todas as cifras fracas conhecidas e conclui o handshake em uma única ida e volta) e mantenha o TLS 1.2 apenas com cipher suites AEAD, como ECDHE-ECDSA-AES128-GCM-SHA256 ou ECDHE-RSA-CHACHA20-POLY1305.
Duas linhas importam quando você testa de fora: o protocolo negociado e Verify return code: 0, que significa que toda a cadeia de certificados foi validada. Um certificado intermediário ausente é a causa mais comum de relatos do tipo "funciona no Chrome, falha no curl e no Android"; nosso guia sobre a cadeia de certificados SSL mostra como corrigir, e o Verificador de SSL aponta diretamente uma cadeia incompleta. Veja a verificação executada contra o dnsrobot.net:
echo | openssl s_client -connect dnsrobot.net:443 -servername dnsrobot.net 2>/dev/null \
| grep -E 'Protocol|Cipher|Verify return'
# Protocol : TLSv1.3
# Cipher : TLS_AES_256_GCM_SHA384
# Verify return code: 0 (ok)
# Confirm the old versions are refused:
openssl s_client -connect dnsrobot.net:443 -tls1_1 2>&1 | grep -iE 'alert|no protocols'HSTS: Nunca Mais Deixe o Navegador Usar HTTP
Redirecionar HTTP para HTTPS não basta por si só. A primeiríssima requisição que um visitante faz para http://yourdomain.com ainda trafega em texto puro, e um atacante na mesma rede pode respondê-la antes do seu redirecionamento. O HTTP Strict Transport Security (RFC 6797) resolve isso: depois que o navegador vê o cabeçalho, ele reescreve toda URL http:// futura do seu domínio para https:// antes de enviar qualquer coisa.
Use um max-age de pelo menos um ano (31536000 segundos), adicione includeSubDomains assim que todos os subdomínios servirem HTTPS e então cadastre o domínio em hstspreload.org para que ele venha embutido no Chrome, Firefox, Safari e Edge. O preload fecha completamente a brecha da primeira visita. Verifique o cabeçalho com o Verificador de Cabeçalhos HTTP, que avalia o HSTS como parte da nota de segurança de A a F.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadCertificados de 47 Dias: Automatize a Renovação Já
A votação SC-081 do CA/Browser Forum está reduzindo em etapas a validade máxima de todo certificado TLS público. O primeiro corte entrou em vigor em 15 de março de 2026, então qualquer certificado que você compre ou renove hoje já está limitado a 200 dias, e em 2029 um certificado precisará ser substituído mais ou menos a cada seis semanas:
| Data de vigência | Validade máxima do certificado | Reutilização da validação de domínio |
|---|---|---|
| Antes de 15 de março de 2026 | 398 dias | 398 dias |
| 15 de março de 2026 | 200 dias | 200 dias |
| 15 de março de 2027 | 100 dias | 100 dias |
| 15 de março de 2029 | 47 dias | 10 dias |
A renovação manual não sobrevive a esse cronograma. A boa prática é a emissão totalmente automatizada via ACME: Let's Encrypt ou Google Trust Services por meio do certbot, acme.sh, Caddy ou dos certificados automáticos embutidos na Cloudflare, Vercel, Netlify e na maioria dos painéis de hospedagem. Teste o caminho de renovação antes de precisar dele (certbot renew --dry-run no certbot); a falha a procurar é um registro CAA ou uma regra de firewall adicionada desde a última renovação que agora bloqueia a validação. Seja qual for a ferramenta, monitore também a data de expiração de fora: o Verificador de SSL mostra os dias restantes, e um certificado expirado transforma cada visita em um aviso de página inteira do navegador.
Camada 3: Cabeçalhos de Segurança HTTP
Os cabeçalhos de segurança são instruções que o seu servidor dá ao navegador sobre o que ele pode ou não fazer com as suas páginas: quais scripts podem rodar, se a página pode ser colocada em um frame, se o navegador pode adivinhar tipos de conteúdo e o que ele deve contar a outros sites sobre a origem de um visitante. Eles não custam nada, se aplicam a todas as páginas de uma vez e bloqueiam classes inteiras de ataque. Também são a camada que a maioria dos sites acerta só em parte, e é por isso que o Verificador de Cabeçalhos HTTP dá uma nota a eles. Veja o que o dnsrobot.net envia, filtrado apenas para os cabeçalhos de segurança:
curl -sI https://dnsrobot.net/ | grep -iE 'strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy'
strict-transport-security: max-age=31536000; includeSubDomains; preload
content-security-policy: default-src 'self'; script-src 'self' ... https://*.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
x-frame-options: DENY
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(self), microphone=(), geolocation=(), payment=(), usb=()Os Sete Cabeçalhos que Todo Site Deve Enviar
Estes são os valores a buscar em um site típico. Os cinco primeiros são os que mudam a sua nota; os dois últimos são acréscimos baratos depois que os outros estiverem no lugar.
| Cabeçalho | Valor recomendado | O que ele previne |
|---|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload | Downgrade de protocolo, roubo de cookies via HTTP |
Content-Security-Policy | script-src baseado em nonce com 'strict-dynamic'; object-src 'none'; base-uri 'none'; frame-ancestors 'none' | Cross-site scripting (XSS), injeção de dados, clickjacking |
X-Content-Type-Options | nosniff | MIME sniffing que transforma um upload em script executável |
Referrer-Policy | strict-origin-when-cross-origin | Vazamento de URLs completas (tokens, termos de busca) para terceiros |
Permissions-Policy | camera=(), microphone=(), geolocation=(), payment=() | Scripts de terceiros usando em silêncio APIs poderosas do navegador |
X-Frame-Options | DENY (fallback legado para frame-ancestors) | Clickjacking em navegadores sem CSP Level 2 |
Cross-Origin-Opener-Policy | same-origin | Ataques entre janelas por meio de window.opener |
Content Security Policy Sem Quebrar o Site
Uma Content Security Policy é a defesa mais eficaz contra XSS, porque impede que scripts injetados sejam executados mesmo quando existe uma falha de injeção. O problema é que uma política rígida quebra qualquer script inline ou tag de terceiros que você tenha esquecido. A abordagem moderna, recomendada pelo Google e documentada no MDN, é uma política baseada em nonce: o servidor gera um nonce aleatório a cada resposta, coloca esse nonce em toda tag de script que renderiza intencionalmente, e o navegador recusa todo o resto.
O 'strict-dynamic' é o que torna a política viável: um script com nonce pode carregar outros scripts (analytics, gerenciadores de tags, widgets) sem que cada um precise estar na allowlist, enquanto os tokens https: e 'unsafe-inline' são ignorados pelos navegadores modernos e servem apenas de fallback para os antigos. Implante primeiro com Content-Security-Policy-Report-Only, acompanhe os relatórios de violação por uma semana e depois passe para o modo de bloqueio. Frameworks como Next.js, Rails e Django têm suporte nativo a nonce; sites estáticos podem usar um script-src baseado em hash.
Content-Security-Policy:
script-src 'nonce-r4nd0m1z3dV4lu3' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
frame-ancestors 'none';
form-action 'self';
report-to csp-endpoint
<!-- Only scripts carrying the nonce run; strict-dynamic lets them load their own dependencies -->
<script nonce="r4nd0m1z3dV4lu3" src="/app.js"></script>Clickjacking: frame-ancestors e X-Frame-Options
O clickjacking carrega o seu site de forma invisível dentro da página de um atacante e induz o visitante a clicar em um botão que ele não consegue ver. frame-ancestors 'none' na CSP (ou 'self', se você incorpora as suas próprias páginas) impede isso em todos os navegadores atuais, e X-Frame-Options: DENY cobre os mais antigos. Se você oferece de propósito um widget incorporável, libere apenas esse caminho e apenas para as origens que precisam dele; nosso guia de X-Frame-Options mostra as regras exatas de servidor e os erros "refused to connect" que você vai encontrar durante os testes.
nosniff, Referrer-Policy, Permissions-Policy e COOP
X-Content-Type-Options: nosniff impede que o navegador tente adivinhar além do Content-Type, que é o que transforma uma "imagem" enviada por um usuário, mas contendo HTML, em uma página executável. Referrer-Policy: strict-origin-when-cross-origin (o padrão nos navegadores atuais, mas defina explicitamente) envia apenas a sua origem para outros sites, de modo que tokens de redefinição de senha e termos de busca nas URLs continuem privados. Permissions-Policy desativa recursos do navegador que você não usa, para que um script de terceiros comprometido não consiga abrir a câmera nem ler a localização. Cross-Origin-Opener-Policy: same-origin corta o vínculo window.opener com as páginas que você abre, o que bloqueia uma classe de ataques entre janelas.
Os cabeçalhos a remover também importam: Server e X-Powered-By anunciam as versões exatas do software para scanners de vulnerabilidades, e X-XSS-Protection está obsoleto e pode introduzir bugs em navegadores antigos, então defina-o como 0 ou remova-o. O Detector de CMS mostra o que um scanner descobre sobre a sua stack só pelos cabeçalhos.
Camada 4: Autenticação que Resiste ao Credential Stuffing
Atacantes raramente adivinham senhas uma a uma. Eles reaproveitam bilhões de pares de e-mail e senha vazados de outros sites (credential stuffing) e conseguem o resto por phishing. Por isso, a boa prática de autenticação tem três partes: armazenar senhas de modo que um vazamento do banco de dados não seja um vazamento de senhas, tornar uma senha roubada inútil por si só e deixar a força bruta lenta demais para fazer diferença. A OWASP classifica Authentication Failures (falhas de autenticação) em A07 no OWASP Top 10:2025.
Advertisement
Faça o Hash de Senhas com Argon2id ou bcrypt
Nunca armazene uma senha, e nunca armazene um hash rápido dela. MD5, SHA-1 e até SHA-256 foram projetados para ser rápidos, então uma tabela vazada com esses hashes pode ser testada a bilhões de tentativas por segundo em uma única GPU. Use uma função de hash de senhas lenta e que exija muita memória, com um salt único por senha. O OWASP Password Storage Cheat Sheet recomenda, nesta ordem:
Argon2id com pelo menos 19 MiB de memória, 2 iterações e grau de paralelismo 1 (ou 46 MiB com 1 iteração).
scrypt com N = 2^17, r = 8, p = 1 onde o Argon2 não estiver disponível.
bcrypt com fator de trabalho 10 ou mais (atenção ao limite de entrada de 72 bytes; fazer pré-hash de senhas mais longas exige cuidado).
PBKDF2-HMAC-SHA256 com 600.000 iterações, apenas quando a conformidade com FIPS exigir.
// Node.js with the argon2 package
import argon2 from "argon2";
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 19456, // KiB = 19 MiB
timeCost: 2,
parallelism: 1,
});
// Later, on login:
const ok = await argon2.verify(hash, submittedPassword);Regras de Senha que Realmente Ajudam (NIST SP 800-63B)
As regras de composição que a maioria dos sites ainda impõe (uma letra maiúscula, um símbolo, troca a cada 90 dias) produzem senhas previsíveis como Summer2026! e levam as pessoas a reutilizá-las. As diretrizes atuais do NIST SP 800-63B as substituem por regras que medem o que importa:
Mínimo de 8 caracteres quando o MFA também é exigido, e pelo menos 15 caracteres para uma senha usada sozinha; permita pelo menos 64.
Nada de regras de composição e nada de troca periódica obrigatória; exija a troca apenas quando houver evidência de comprometimento.
Confira toda senha nova em uma lista de vazamentos (a API de intervalos do Have I Been Pwned permite fazer isso sem enviar a senha) e rejeite as que já vazaram.
Permita colar senhas e usar gerenciadores de senhas, aceite espaços e Unicode e mostre um medidor de força baseado em entropia, e não em regras.
Nosso Testador de Força de Senha mostra como essas medidas diferem das regras antigas: ele estima o tempo de quebra a partir da entropia e da detecção de padrões e confere a senha em dados de vazamentos, um retorno muito mais útil do que uma mensagem vermelha de "precisa de um símbolo".
MFA e Passkeys
A autenticação multifator transforma uma senha roubada em um beco sem saída. Ofereça para todos e exija para os perfis de administração, financeiro e suporte. Classifique as opções pela resistência a phishing: passkeys e chaves de segurança FIDO2 não podem ser alvo de phishing porque a credencial fica vinculada à sua origem real; apps autenticadores (TOTP) são bons; códigos por SMS são melhores do que nada, mas podem ser interceptados via SIM swap. As passkeys são suportadas por todos os navegadores e sistemas operacionais atuais e eliminam a senha por completo, o que também elimina o credential stuffing como categoria.
Proteja o caminho de recuperação com o mesmo cuidado que o login: códigos de recuperação armazenados como hash, redefinições por e-mail com tokens de uso único que expiram em até 15 minutos e nada de perguntas de segurança.
Rate Limiting, Bloqueio de Conta e Defesa Contra Bots
Aplique rate limiting aos endpoints de login, cadastro, redefinição de senha e MFA por IP e por conta, e retorne 429 Too Many Requests com um cabeçalho Retry-After quando o limite for atingido (nosso guia sobre o erro HTTP 429 explica como os clientes devem reagir). Combine isso com um atraso crescente ou um bloqueio temporário após falhas repetidas em uma mesma conta, e com um CAPTCHA ou desafio de proof-of-work nos endpoints que sofrem ataques distribuídos. Registre todo login com falha junto com o IP de origem, para que o padrão de uma rodada de credential stuffing fique visível em minutos, e não em meses.
# nginx: 5 login attempts per minute per client IP
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
location = /api/login {
limit_req zone=login burst=4 nodelay;
limit_req_status 429;
proxy_pass http://app;
}Sessões, Cookies e CSRF
Depois do login, o cookie de sessão é o usuário, então ele merece a mesma proteção que a senha. Três atributos de cookie e um prefixo de nome fazem a maior parte do trabalho:
Securesignifica que o cookie nunca é enviado por HTTP puro, então ele não pode ser capturado em um Wi-Fi público.HttpOnlydeixa o cookie invisível para o JavaScript, então uma falha de XSS não consegue lê-lo.SameSite=Lax(ouStrictpara painéis de administração) mantém o cookie fora de requisições POST entre sites, o que neutraliza a maioria dos ataques CSRF.O prefixo
__Host-faz o navegador recusar o cookie a menos que ele sejaSecure, tenhaPath=/e não tenha o atributoDomain, para que um subdomínio não consiga sobrescrevê-lo.
Set-Cookie: __Host-session=9f1c7e2a...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=28800CSRF: Uma Segunda Linha de Defesa Além do SameSite
O cross-site request forgery (CSRF) engana um navegador logado para que ele envie ao seu site, a partir de outra página, uma requisição que altera estado. Cookies SameSite impedem o caso comum, mas mantenha uma segunda defesa em toda requisição que não seja GET: um token anti-CSRF por sessão em um campo oculto ou em um cabeçalho personalizado, ou uma verificação de que o cabeçalho Origin (ou Sec-Fetch-Site) corresponde à sua própria origem. A maioria dos frameworks (Django, Rails, Laravel, server actions do Next.js) faz isso por padrão; o erro é desativar a proteção em um endpoint de API e depois chamar esse endpoint a partir de um navegador.
IDs de Sessão, Rotação e JWTs
Gere IDs de sessão com pelo menos 128 bits de aleatoriedade, troque o ID no login e em qualquer mudança de privilégio para neutralizar a fixação de sessão, expire as sessões após um período de inatividade e dê aos usuários um botão "sair de todos os dispositivos" que as invalide no servidor. Com JWTs, mantenha-os de curta duração (minutos, com um refresh token que possa ser revogado), nunca os armazene no localStorage, onde qualquer script consegue lê-los, e trate o vazamento de uma chave de assinatura como uma violação completa, porque todo token que ela já assinou agora pode ser forjado.
Injeção e XSS: Valide a Entrada, Codifique a Saída
A Injection (injeção, A05:2025) e o cross-site scripting são o mesmo erro em lugares diferentes: dados vindos de um usuário são entregues a um interpretador (SQL, o shell, uma consulta LDAP, uma página HTML) como se fossem código. A correção também é a mesma em todo lugar: mantenha dados e código separados e nunca monte um comando concatenando strings.
Advertisement
Consultas Parametrizadas, Não Concatenação de Strings
Em SQL, use prepared statements ou um ORM que os gere; o banco de dados passa a tratar a entrada como um valor que nunca pode alterar a estrutura da consulta. A mesma regra vale para comandos de shell (passe um array de argumentos, nunca uma string), para filtros NoSQL (rejeite objetos de operador como {"$gt": ""} na entrada do usuário) e para caminhos de arquivo (resolva o caminho e confirme que ele permanece dentro do diretório pretendido).
// Vulnerable: the input becomes part of the query text
const rows = await db.query(`SELECT * FROM users WHERE email = '${email}'`);
// Safe: the driver sends the value separately from the query
const rows = await db.query("SELECT * FROM users WHERE email = $1", [email]);
// Python / psycopg: same idea
cur.execute("SELECT * FROM users WHERE email = %s", (email,))Codificar a Saída Impede o XSS
O cross-site scripting acontece quando um texto controlado pelo usuário é escrito em uma página sem a codificação adequada ao contexto em que ele cai. Os template engines modernos (React e JSX, Vue, Jinja2, Blade, ERB) codificam por padrão; hoje, o XSS costuma vir das válvulas de escape: dangerouslySetInnerHTML, v-html, |safe, innerHTML e a montagem de URLs ou event handlers inline a partir de dados do usuário. Codifique para o contexto exato e sanitize HTML rico com uma biblioteca feita para isso, como o DOMPurify, em vez de uma regex.
| Onde os dados caem | Codifique como | Exemplo |
|---|---|---|
| Corpo do HTML | Entidades HTML | < vira <, " vira " |
| Atributo HTML | Entidades, com o atributo entre aspas | Sempre coloque o atributo entre aspas; codifique " e ' |
| JavaScript | Não injete no texto do script; passe os dados por atributos data- ou por um bloco JSON <script type="application/json"> | </script> dentro de uma string vira \u003c/script\u003e |
| Parâmetro de URL | Percent-encoding | encodeURIComponent(value) |
| Valor CSS | Evite totalmente; escolha nomes de classe de uma allowlist | Nunca interpole entrada do usuário em style |
SSRF, Upload de Arquivos e Desserialização
Server-side request forgery (SSRF): se o seu servidor busca uma URL fornecida por um usuário (webhooks, proxies de imagem, pré-visualizações de links), um atacante pode apontá-la para serviços internos ou para o endpoint de metadados da nuvem 169.254.169.254 e ler credenciais. Resolva o hostname primeiro, rejeite faixas privadas e link-local, desative redirecionamentos e coloque os serviços que fazem essas buscas em um segmento de rede que não alcance nada interno.
Upload de arquivos: valide o tipo inspecionando o conteúdo, e não a extensão nem o Content-Type informado pelo cliente; imponha um limite de tamanho; armazene os arquivos fora da raiz web ou em object storage, com um nome aleatório gerado por você; e sirva-os a partir de uma origem separada com nosniff e Content-Disposition: attachment sempre que possível. Nunca deixe um upload cair em um lugar onde o servidor web vá executá-lo.
Desserialização e mass assignment: nunca desserialize dados não confiáveis com um formato capaz de instanciar classes arbitrárias (serialização nativa do Java, pickle do Python, unserialize do PHP, YAML com tags personalizadas); use JSON com um schema. Vincule os corpos das requisições a uma allowlist explícita de campos, para que um usuário não consiga enviar via POST "role": "admin" para um modelo que por acaso tenha essa coluna.
Broken Access Control: O Risco Número Um
O Broken Access Control (controle de acesso quebrado) ocupa o primeiro lugar do OWASP Top 10 desde 2021 e continua nele como A01:2025. Não é um bug isolado, mas um hábito: verificar quem a pessoa é (autenticação) e esquecer de verificar o que ela pode acessar (autorização) em cada requisição. A forma clássica é a referência direta insegura a objeto (IDOR): GET /api/invoices/1042 funciona para o dono da fatura e também para qualquer pessoa que troque o número.
Negue por padrão. Toda rota exige uma regra explícita que conceda acesso; uma regra ausente significa 403, não 200.
Imponha no servidor, objeto por objeto. Esconder um botão na interface não é controle de acesso. Toda leitura e escrita deve confirmar que o usuário atual é dono do registro específico ou tem permissão sobre ele, inclusive em endpoints em lote, exportações e jobs em segundo plano.
Use IDs impossíveis de adivinhar quando isso ajudar (UUIDs), mas nunca dependa deles: obscuridade não é autorização.
Restrinja o CORS.
Access-Control-Allow-Origin: *com credenciais, ou refletir qualquerOriginque a requisição envie, entrega a sua API a qualquer site que o usuário visite.Desative a listagem de diretórios, bloqueie
.git,.env, arquivos de backup e de configuração no nível do servidor web e mantenha os painéis de administração fora da internet pública ou protegidos por MFA e allowlists de IP.Aplique rate limiting e registre as falhas de autorização. Uma rajada de 403 vinda de uma única sessão é um ataque de enumeração em andamento.
// Express: ownership check on every object access
app.get("/api/invoices/:id", requireAuth, async (req, res) => {
const invoice = await Invoice.findById(req.params.id);
if (!invoice || invoice.ownerId !== req.user.id) {
return res.status(404).end(); // 404, not 403: don't confirm the record exists
}
res.json(invoice);
});Camada 5: Dependências e a Cadeia de Suprimentos de Software
Uma aplicação web típica tem talvez 5% de código que você escreveu e 95% de código que você baixou. É por isso que Software Supply Chain Failures (falhas na cadeia de suprimentos de software) estreia em A03 no OWASP Top 10 de 2025, e por isso a exploração de vulnerabilidades superou as credenciais roubadas no DBIR 2026. Em setembro de 2025, um único mantenedor vítima de phishing colocou um malware que rouba criptomoedas no chalk, no debug e em outros 16 pacotes npm que, juntos, somam mais de dois bilhões de downloads por semana; todo projeto que instalou uma versão não fixada durante essa janela recebeu o malware automaticamente.
Faça commit de um lockfile (
package-lock.json,yarn.lock,pnpm-lock.yaml,poetry.lock,go.sum) e instale comnpm cino CI, para que os builds sejam reproduzíveis e uma nova versão upstream não entre sem revisão.Escaneie continuamente:
npm audit,pip-audit,bundler-audit, GitHub Dependabot ou Renovate para as atualizações e um scanner de contêineres como Trivy ou Grype para a camada do sistema operacional.Adie as atualizações que não são de segurança por alguns dias (o
minimumReleaseAgedo Renovate), para que uma versão envenenada normalmente seja retirada antes de você adotá-la, mas aplique os patches de segurança imediatamente.Fixe actions e scripts de terceiros no SHA de um commit no CI e use Subresource Integrity (
integrity="sha384-...") em todo script que você carrega de uma CDN pública.Dê ao CI o menor privilégio possível: tokens somente leitura sempre que der, credenciais OIDC de curta duração em vez de segredos de longa duração e direitos de publicação limitados a um job de release.
Gere um SBOM (CycloneDX ou SPDX) no momento do build, para responder "fomos afetados?" em minutos quando chegar a próxima CVE do porte do Log4Shell.
# Reproducible install + audit in CI
npm ci --ignore-scripts
npm audit --audit-level=high
# Pin a GitHub Action to a commit, not a floating tag
# uses: actions/checkout@v4 <- can change underneath you
# uses: actions/checkout@<full-commit-sha> # v4.2.2
# Subresource Integrity for a CDN script (hash from: openssl dgst -sha384 -binary lib.min.js | openssl base64 -A)
<script src="https://cdn.example.com/lib.min.js"
integrity="sha384-<base64-hash>"
crossorigin="anonymous"></script>Se você usa WordPress ou outro CMS, as mesmas regras valem para plugins e temas, que é por onde começa a maioria dos comprometimentos de CMS. Mantenha o core e todos os plugins atualizados, exclua os que você não usa e veja o que um scanner consegue descobrir sobre as suas versões com o Detector de CMS.
Camada 6: Hardening do Servidor (Portas, Patches, Segredos, Backups)
A camada da aplicação não importa se o servidor por baixo aceita login por senha no SSH, roda um banco de dados em uma porta pública ou não recebe patches desde que foi montado. As boas práticas de servidor são entediantes, e é por elas que o ransomware entra.
Feche Todas as Portas que Você Não Usa
Um servidor web público precisa das portas 80 e 443 abertas, e do SSH liberado apenas para a sua própria faixa de IPs. Bancos de dados (3306, 5432, 27017), Redis (6379), Elasticsearch (9200), painéis de administração e endpoints de métricas devem escutar apenas em localhost ou em uma rede privada. Confira de fora com o Verificador de Portas após cada mudança, porque essa é a visão que um atacante tem, e é fácil interpretar errado os security groups da nuvem.
# Ubuntu: default-deny firewall, allow only web + SSH from your office range
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp
sudo ufw enable
# SSH: keys only, no root password login
sudo sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
sudo systemctl reload sshAplique Patches com Regularidade e Tire os Segredos do Código
Ative as atualizações de segurança automáticas do sistema operacional, assine as listas de segurança do seu runtime e do seu framework e trate uma CVE crítica em qualquer coisa exposta à internet como tarefa para o mesmo dia. Rode os serviços com usuários sem privilégios, em contêineres ou com o sandboxing do systemd, para que um processo comprometido não consiga ler a máquina inteira.
Segredos (senhas de banco de dados, chaves de API, chaves de assinatura) nunca devem ficar no repositório, em uma camada de imagem Docker ou no JavaScript do lado do cliente. Carregue-os de variáveis de ambiente injetadas no deploy ou de um gerenciador de segredos, troque-os quando alguém com acesso sair da equipe e escaneie o histórico do repositório com uma ferramenta como o gitleaks, porque uma chave commitada uma única vez está comprometida para sempre, mesmo depois que o commit é removido.
Backups que Sobrevivem ao Ransomware, e uma CDN na Frente
Siga a regra 3-2-1: três cópias, em duas mídias diferentes, uma fora do local, e deixe pelo menos uma cópia imutável ou offline, para que um ransomware que chegue ao servidor não consiga criptografar também os backups. Criptografe os backups, inclua o banco de dados e o diretório de uploads e, o mais importante, teste uma restauração periodicamente. Um backup que ninguém nunca restaurou é uma esperança, não um plano. O tempo de recuperação é o que decide se um incidente será uma simples indisponibilidade ou um evento capaz de acabar com a empresa.
Coloque uma CDN ou WAF (Cloudflare, Fastly, AWS CloudFront com WAF ou o equivalente da sua hospedagem) na frente da origem. Ela absorve DDoS volumétrico, aplica regras gerenciadas contra payloads de injeção comuns e bots maliciosos, esconde o IP da sua origem e oferece um lugar para impor rate limits e regras geográficas sem mexer na aplicação. Depois, restrinja o firewall da origem para que apenas as faixas de IP da CDN possam acessar a porta 443 diretamente; caso contrário, os atacantes simplesmente passam por fora dela. O Verificador de Hospedagem mostra se a origem real de um site está exposta por trás da CDN.
Autenticação de E-mail: SPF, DKIM e DMARC
A segurança web inclui o e-mail que leva as suas redefinições de senha, faturas e respostas de suporte. Sem SPF, DKIM e DMARC, qualquer pessoa pode enviar e-mails como billing@yourdomain.com, e desde fevereiro de 2024 o Google e o Yahoo exigem os três de quem envia e-mails em massa. O conjunto completo são três registros DNS TXT:
yourdomain.com. TXT "v=spf1 include:_spf.google.com -all"
google._domainkey.yourdomain.com. TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkq..."
_dmarc.yourdomain.com. TXT "v=DMARC1; p=reject; rua=mailto:dmarc@yourdomain.com; adkim=s; aspf=s"Comece o DMARC em p=none para coletar relatórios e passe para p=quarantine e depois para p=reject quando todos os remetentes legítimos (plataforma de marketing, helpdesk, CRM) passarem no alinhamento. Adicione registros MTA-STS e TLS-RPT para forçar a entrega criptografada aos seus servidores de e-mail e publique um MX nulo (MX 0 .) nos domínios que nunca enviam e-mails, para que eles não possam ser falsificados de forma alguma. Verifique cada registro com o Verificador de SPF, o Verificador de DKIM e o Verificador de DMARC, e confira os seus IPs de envio em listas de bloqueio com o Verificador de Blacklist de IP se a entregabilidade cair.
Registre Logs, Monitore e Prepare-se para a Invasão
Duas das dez categorias do OWASP 2025 tratam do que acontece depois de um erro: Security Logging and Alerting Failures (falhas de log e alerta de segurança, A09) e a nova Mishandling of Exceptional Conditions (tratamento inadequado de condições excepcionais, A10). A invasão típica ainda passa meses sem ser detectada porque as evidências nunca foram registradas, ou foram registradas e ninguém olhou.
Registre eventos de segurança com contexto: todo login bem-sucedido ou com falha, alterações de MFA, redefinições de senha, mudanças de permissão, negações de controle de acesso, falhas de validação de entrada e ações administrativas, cada um com data e hora, ID do usuário, IP de origem e user agent.
Nunca registre segredos em log: senhas, tokens de sessão, números completos de cartão, chaves de API. Mascare esses dados na camada de logging, e não em cada ponto de chamada.
Envie os logs para fora do servidor, para um armazenamento central (CloudWatch, Loki, Elastic, um SIEM) com retenção de pelo menos 90 dias, para que um atacante com root não consiga apagar os próprios rastros.
Crie alertas para padrões, não para eventos isolados: 50 logins com falha em um minuto, um pico de 403 vindo de uma sessão, um novo administrador criado fora do horário comercial, um certificado emitido para o seu domínio que você não solicitou.
Falhe fechado: quando uma verificação downstream (serviço de autenticação, rate limiter, WAF) der erro, negue a requisição. A A10 existe porque muitos sistemas concedem acesso quando a verificação que deveria bloqueá-lo lança uma exceção.
Monitore de fora: uptime, expiração de certificados, mudanças no DNS, inclusões em listas de bloqueio e regressões de cabeçalhos. A verificação de Saúde do Domínio reúne as checagens de DNS, SSL, autenticação de e-mail e cabeçalhos em uma única nota que você pode gerar de novo após cada deploy.
Escreva o Plano de Resposta a Incidentes Antes de Precisar Dele
Decida com antecedência quem fica de plantão, como trocar todas as credenciais, como colocar o site em modo somente leitura, onde estão os backups limpos e quais órgãos reguladores ou clientes precisam ser notificados, e em quantas horas (72 pelo GDPR). Faça um exercício de mesa (tabletop) uma vez por ano. Equipes com um plano ensaiado se recuperam em dias; equipes sem plano improvisam por semanas.
Teste Tudo: Scanners, Pentests e Verificações Contínuas
Todos os controles acima podem ser verificados, e a verificação deve ser automatizada sempre que possível, para que não se degrade com o tempo. Uma escada razoável, do grátis e instantâneo ao periódico e pago:
| Verificação | Ferramenta | Com que frequência |
|---|---|---|
| Configuração, cadeia e expiração do TLS | Verificador de SSL, SSL Labs, testssl.sh | Após cada mudança de certificado ou de servidor; expiração diariamente |
| Cabeçalhos de segurança e CSP | Verificador de Cabeçalhos HTTP, MDN HTTP Observatory | Após cada deploy (coloque no CI) |
| Portas abertas | Verificador de Portas, nmap | Após cada mudança de firewall ou na nuvem |
| DNS, DNSSEC, autenticação de e-mail | Saúde do Domínio, Verificador de DMARC | Mensalmente e após qualquer edição no DNS |
| Subdomínios esquecidos | Localizador de Subdomínios | A cada trimestre e em toda desativação de serviço |
| Dependências com vulnerabilidades conhecidas | npm audit, Dependabot, Trivy | A cada build |
| Falhas no código (SAST) | Semgrep, CodeQL | A cada pull request |
| Vulnerabilidades web em tempo de execução (DAST) | OWASP ZAP, Nuclei | Semanalmente, no ambiente de staging |
| Autorização e lógica de negócio | Pentest manual ou programa de bug bounty | Anualmente e após grandes funcionalidades |
Scanners são bons em encontrar injeção, problemas de cabeçalhos e componentes desatualizados, e ruins em autorização e lógica de negócio, então reserve orçamento para pelo menos um teste feito por uma pessoa por ano em tudo o que lida com dinheiro ou dados pessoais. Corrija por nível de exposição: qualquer coisa que um usuário não autenticado consiga explorar pela internet vem primeiro.
Checklist de Segurança Web
Copie isto para o README do seu projeto ou para o seu gerenciador de tarefas. Cada linha é uma das práticas acima, redigida para ser marcada como feita ou não feita; a última coluna é a prova.
| Camada | Prática | Concluído quando |
|---|---|---|
| Domínio | Registrar lock e MFA na conta do registrador; renovação automática ativada | O WHOIS mostra clientTransferProhibited; expiração a mais de 12 meses |
| DNS | DNSSEC assinado; registro CAA listando apenas as suas CAs | dig +dnssec retorna RRSIG; registro DS presente na zona pai |
| DNS | Nenhum registro CNAME ou A órfão | Todo hostname do Localizador de Subdomínios resolve para algo que você controla |
| TLS | Apenas TLS 1.2+, TLS 1.3 de preferência, cadeia completa servida | Verificador de SSL: cadeia completa, TLS 1.0/1.1 recusados |
| TLS | HSTS com max-age de um ano, includeSubDomains, na lista de preload | Listado no hstspreload.org |
| TLS | Renovação automatizada via ACME; expiração monitorada | certbot renew --dry-run passa; alerta configurado para 14 dias |
| Cabeçalhos | CSP (baseada em nonce), frame-ancestors, nosniff, Referrer-Policy, Permissions-Policy | Nota A no Verificador de Cabeçalhos HTTP |
| Autenticação | Hash com Argon2id ou bcrypt; regras de senha do NIST; verificação em lista de vazamentos | Um hash leva de 100 a 250 ms; nenhuma regra de composição |
| Autenticação | MFA oferecido a todos e obrigatório para administradores; passkeys suportadas | Login de administrador impossível sem um segundo fator |
| Autenticação | Endpoints de login, redefinição e MFA com rate limiting por IP e por conta | A sexta tentativa em um minuto retorna 429 |
| Sessões | Cookie __Host- com Secure, HttpOnly e SameSite; ID trocado no login | Atributos do cookie visíveis no DevTools; sessão antiga inválida após o login |
| Entrada | Consultas parametrizadas em todo lugar; saída codificada por contexto; uploads validados pelo conteúdo | Nenhuma consulta montada com strings em uma busca no código; payloads de XSS aparecem como texto |
| Controle de acesso | Roteamento que nega por padrão; verificação de propriedade por objeto; CORS restrito | Repetir requisições com os IDs de outro usuário retorna 404 ou 403 |
| Dependências | Lockfile commitado; npm ci; auditoria no CI; actions fixadas por SHA; SRI em scripts de CDN | O build falha com uma CVE de alta severidade |
| Servidor | Apenas 80 e 443 públicas; SSH só com chaves; atualizações de segurança automáticas; segredos em variáveis de ambiente ou em um cofre | O Verificador de Portas mostra 22, 3306 e 6379 fechadas a partir da internet |
| Backups | 3-2-1 com uma cópia imutável; restauração testada | Último teste de restauração bem-sucedido feito há menos de 90 dias |
SPF -all, DKIM, DMARC p=reject, MTA-STS | O Verificador de DMARC aprova; relatórios agregados chegando | |
| Monitoramento | Eventos de autenticação e autorização registrados de forma centralizada; alertas para padrões; monitoramento externo de uptime, certificados e DNS | Alerta de teste disparado e recebido |
| Resposta | Plano de incidentes por escrito; security.txt publicado; exercício tabletop realizado | Plano revisado nos últimos 12 meses |
Como Isso se Relaciona com o OWASP Top 10:2025
O OWASP Top 10 é a lista de riscos de aplicações web mais citada, e a edição de 2025 reorganizou a ordem e introduziu duas categorias novas. Se um cliente, auditor ou framework de compliance perguntar como você lida com ela, esta é a correspondência com as práticas deste guia:
| OWASP Top 10:2025 | Práticas neste guia |
|---|---|
| A01 Broken Access Control | Negar por padrão, verificações por objeto, CORS restrito, painéis de administração protegidos |
| A02 Security Misconfiguration | Cabeçalhos de segurança, HSTS, portas fechadas, cabeçalhos de versão removidos, listagem de diretórios desativada |
| A03 Software Supply Chain Failures (nova) | Lockfiles, auditorias, actions fixadas, SRI, SBOM, CI com privilégio mínimo |
| A04 Cryptographic Failures | TLS 1.2+ e 1.3, HSTS, Argon2id, gestão de segredos, backups criptografados |
| A05 Injection | Consultas parametrizadas, codificação de saída, CSP, validação de uploads |
| A06 Insecure Design | Modelagem de ameaças por camada, padrões que falham fechados, rate limiting desde o design |
| A07 Authentication Failures | Regras de senha do NIST, verificação de vazamentos, MFA e passkeys, rotação de sessão |
| A08 Software or Data Integrity Failures | SRI, dependências assinadas e fixadas, desserialização segura |
| A09 Security Logging and Alerting Failures | Logs centralizados, alertas por padrão de comportamento, monitoramento externo |
| A10 Mishandling of Exceptional Conditions (nova) | Falhar fechado, respostas de erro uniformes, nenhum stack trace exibido aos usuários |
Verifique os cabeçalhos de segurança do seu site em segundos
O Verificador de Cabeçalhos HTTP grátis do DNS Robot acessa qualquer URL e dá uma nota de A a F aos cabeçalhos de segurança: HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy e Permissions-Policy, mostrando o valor exato encontrado e o que está faltando. Sem cadastro.
Testar Verificador de Cabeçalhos HTTPAdvertisement
Perguntas Frequentes sobre Boas Práticas de Segurança Web
Force HTTPS com TLS moderno e HSTS, envie cabeçalhos de segurança rígidos (principalmente uma Content Security Policy), faça o hash das senhas com Argon2id, protegido por MFA e rate limiting, use consultas parametrizadas e codificação de saída, imponha o controle de acesso no servidor para cada objeto, mantenha as dependências atualizadas e fixadas, feche as portas sem uso e registre os eventos de segurança de forma centralizada. Proteja o domínio e o DNS primeiro, porque todo o resto depende de você ainda controlar o seu nome.