Security headers são, ao mesmo tempo, a linha de defesa mais barata e a mais ignorada da web. Não são código, não são uma biblioteca nova, não exigem refatorar a aplicação: são instruções que o servidor manda junto na resposta HTTP dizendo ao navegador “trate este site com estas regras de segurança”. Uma linha de configuração e o navegador passa a recusar ser colocado dentro de um iframe, a só aceitar HTTPS, ou a bloquear um script injetado. É defesa que acontece no cliente, de graça, sem você tocar no backend.

E mesmo sendo tão barato, a maioria dos sites tira nota baixa. Em auditorias do dia a dia, é comum encontrar aplicações em produção, lidando com dados sensíveis, que não enviam um único cabeçalho de segurança. O resultado é que ataques que deveriam ser impossíveis ou caros ficam triviais: clickjacking, downgrade de HTTPS, roubo de sessão a partir de um XSS que, com a configuração certa, nem teria saído do papel.

Para facilitar o diagnóstico, a IntruderLabs lançou uma ferramenta gratuita que cola os headers do seu site, dá uma nota de A+ a F e gera o trecho de configuração já pronto para a sua stack: o Analisador de Security Headers. Use enquanto lê este artigo. Abaixo, o que cada cabeçalho realmente protege, onde estão as pegadinhas, e como a ausência de cada um vira exploração num pentest de verdade.

O que cada header de verdade protege

Content-Security-Policy (CSP)

CSP é a defesa mais forte que existe contra XSS no navegador. Ele diz ao browser de onde scripts, estilos, imagens e frames podem vir — e bloqueia o resto. Um bom ponto de partida:

Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

Isso já fecha plugins (object-src 'none'), impede sequestro da URL base (base-uri 'self') e bloqueia o site de ser embutido em iframes (frame-ancestors 'none').

A pegadinha: CSP só vale alguma coisa se você não furar a própria política. No momento em que você adiciona script-src 'unsafe-inline' — ou pior, script-src * — para “fazer o site funcionar”, a proteção contra XSS evapora, porque é exatamente scripts inline e de origem arbitrária que o atacante quer executar. O caminho certo é usar nonce (script-src 'nonce-aBc123') ou hash dos scripts confiáveis. Só um cuidado: o nonce precisa ser gerado a cada requisição, aleatório e imprevisível (uns 128 bits em base64) — um valor fixo no código não protege nada. Dá mais trabalho, mas é a diferença entre ter CSP e ter um header decorativo. E lembre: CSP mitiga XSS, não conserta a falha de origem — sanitização e output encoding continuam obrigatórios.

Strict-Transport-Security (HSTS)

HSTS força o navegador a só falar HTTPS com o seu domínio, fechando ataques de SSL-strip e downgrade onde alguém na rede empurra a vítima de volta para HTTP em texto claro.

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

max-age em segundos (aqui, dois anos), includeSubDomains para cobrir todos os subdomínios e preload para entrar na lista embutida dos navegadores.

A pegadinha: um max-age curto (tipo algumas horas) deixa janelas de downgrade abertas demais. E o ponto que quase todo mundo erra: HSTS não protege o primeiríssimo acesso. O navegador só passa a forçar HTTPS depois de ver o header uma vez. Esse primeiro request continua vulnerável a downgrade — a única forma de fechar essa janela é estar na HSTS preload list, que vem compilada no navegador. Por isso a diretiva preload e o cadastro manual no site da lista importam: mandar a diretiva é só o pré-requisito; a entrada só vale depois de submetida e aprovada (e sair da lista depois é lento). Sem isso, o primeiro acesso de cada usuário fica exposto.

X-Frame-Options / CSP frame-ancestors

Esses são a defesa anti-clickjacking — impedem que seu site seja carregado dentro de um iframe num domínio malicioso que sobrepõe a interface para roubar cliques.

X-Frame-Options: DENY

A versão moderna e mais expressiva é a diretiva frame-ancestors 'none' dentro do CSP, que faz o mesmo papel e ainda permite uma allowlist de origens quando você precisa (frame-ancestors 'self' https://parceiro.com). Nos navegadores atuais, quando os dois estão presentes, o frame-ancestors tem precedência sobre o X-Frame-Options; o ideal é mandar os dois para cobrir clientes antigos. Uma observação importante: o antigo ALLOW-FROM do X-Frame-Options está morto — nunca foi suportado direito e foi removido dos navegadores. Se você precisa liberar uma origem específica, use frame-ancestors. Entramos em detalhe no artigo sobre clickjacking.

X-Content-Type-Options: nosniff

X-Content-Type-Options: nosniff

Esse desliga o MIME-sniffing: o comportamento do navegador de “adivinhar” o tipo de um arquivo ignorando o Content-Type declarado. Sem nosniff, um arquivo que você serviu como texto pode ser reinterpretado como JavaScript e executado — o caso clássico é um upload de usuário, servido com um Content-Type genérico ou errado, que o navegador “fareja” como HTML e roda. nosniff é, aliás, o único valor válido desse header.

Referrer-Policy

Referrer-Policy: strict-origin-when-cross-origin

Controla quanto da URL de origem vai no cabeçalho Referer quando o usuário sai do seu site. O valor recomendado manda a URL completa dentro da mesma origem e só a origem (sem path nem query) ao navegar para fora. A pegadinha é unsafe-url, que envia a URL inteira para qualquer destino — e se você tem tokens, IDs de sessão ou de reset de senha na query string, eles vazam para sites de terceiros e param nos logs deles.

Permissions-Policy

Permissions-Policy: geolocation=(), camera=(), microphone=(), payment=(), usb=()

Desliga APIs poderosas do navegador que a sua aplicação não usa. Se o site não precisa de câmera, microfone ou geolocalização, negue tudo: assim, mesmo que um script de terceiro (ou um XSS) tente acessá-las, o navegador recusa. Cada par feature=() com lista vazia significa “ninguém pode usar isso, nem eu”.

COOP / CORP / COEP

Trio de isolamento de origem cruzada, para fechar ataques de canal lateral (tipo Spectre) e vazamentos entre janelas:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin

COOP separa o seu contexto de janela de outras origens; CORP controla quem pode embutir seus recursos. O COEP (require-corp) é opcional e mais delicado: junto com o COOP ele habilita o cross-origin isolation (necessário para SharedArrayBuffer, medição de memória e timers de alta resolução), mas exige que todo recurso cross-origin venha com o cabeçalho certo, o que costuma quebrar embeds, widgets e imagens de terceiros. Só ligue COEP se você precisa desses recursos e está disposto a auditar todas as origens externas.

O que você deveria REMOVER

Nem tudo é adicionar header — alguns precisam sair. A regra é: não entregue de graça informação que ajude o atacante a escolher o exploit.

  • Server: Apache/2.4.58 — a versão exata do servidor é um atalho para buscar CVEs específicas daquela build.
  • X-Powered-By: PHP/8.1.2 — entrega a linguagem e a versão.
  • X-AspNet-Version e X-AspNetMvc-Version — idem para o stack .NET.

Nenhum desses cabeçalhos faz nada pela sua aplicação; eles só economizam o trabalho de reconhecimento do atacante. Remova a versão (ou o header inteiro) na configuração do servidor. Não é segurança por obscuridade substituindo defesa real — é só não deixar o mapa do tesouro na porta.

Cookies também são headers

Set-Cookie é um cabeçalho HTTP, e os atributos certos nele são parte do seu perímetro de segurança:

Set-Cookie: sessionid=abc123; Secure; HttpOnly; SameSite=Lax
  • Secure — o cookie só é enviado por HTTPS, nunca em texto claro.
  • HttpOnly — o cookie fica invisível para JavaScript. Esse é decisivo: sem HttpOnly, um XSS lê document.cookie e exfiltra a sessão inteira. Com HttpOnly, mesmo um XSS bem-sucedido não consegue roubar o cookie de sessão diretamente.
  • SameSite=Lax — o navegador não envia o cookie em requisições cross-site de terceiros, o que é uma base anti-CSRF importante. Se você precisa mesmo de SameSite=None (cenários cross-site legítimos), saiba que ele exige o atributo Secure — sem ele, o navegador rejeita o cookie. Detalhamos o ataque no artigo sobre CSRF; só lembre que SameSite mitiga, não substitui tokens anti-CSRF.

Como a ausência vira exploração no pentest

Headers ausentes raramente são o “grande achado” sozinhos. O que eles fazem é destravar ou amplificar outros ataques. Na prática, é assim que a falta de cada um aparece num teste:

Sem X-Frame-Options nem frame-ancestors: montamos uma página de prova de conceito com seu site dentro de um <iframe> transparente sobre botões falsos. O usuário acha que está clicando na nossa isca e, na verdade, está clicando em “Confirmar transferência” ou “Autorizar app” no seu site logado. Clickjacking funcional, com PoC em HTML que cabe num e-mail.

Sem nosniff: procuramos qualquer ponto que reflita ou armazene conteúdo controlado pelo usuário — um upload de arquivo, um campo que devolve texto. Servindo um payload com Content-Type ambíguo, o navegador faz sniffing e executa como script. Um upload que deveria ser inerte vira XSS.

Sem HSTS (ou sem preload): num cenário de rede hostil (Wi-Fi público, atacante na mesma LAN), interceptamos o primeiro acesso HTTP e fazemos SSL-strip, mantendo a vítima em HTTP em texto claro e capturando credenciais. Mesmo com HSTS ativo, se o domínio não está na preload list, esse primeiro request continua sendo a brecha.

Sem Referrer-Policy adequada: caçamos URLs com tokens na query string (reset de senha, convites, links assinados). Se o site carrega um recurso ou link externo a partir dessas páginas, o token vaza no Referer para o terceiro — e fica nos logs dele. Sequestro de token sem nem tocar no servidor alvo.

Com CSP fraco (ou ausente): quando achamos um XSS, o CSP é o que decide se ele é Low ou crítico. Sem CSP, o payload roda livre — exfiltra dados, faz pivot, instala keylogger. Com CSP forte e nonce, o mesmo payload é bloqueado antes de executar. Com CSP “presente mas furado” ('unsafe-inline'), tratamos como se não houvesse: a injeção passa igual.

Sem HttpOnly no cookie de sessão: combinado com qualquer XSS, lemos document.cookie e mandamos a sessão para um servidor nosso. Escalada direta de “XSS refletido” para “sequestro de conta”, sem precisar de mais nada.

Repare no padrão: quase todo achado de header isolado é Low ou Info, mas cada um remove uma camada que torna outro ataque mais barato. É por isso que eles aparecem no relatório.

Resumo para o relatório

Achado: Ausência de cabeçalhos de segurança HTTP

Impacto: A aplicação não envia cabeçalhos de segurança que instruem o navegador a aplicar proteções contra clickjacking, downgrade de HTTPS, MIME-sniffing e vazamento de informação via Referer. Isoladamente não comprometem a aplicação, mas reduzem as barreiras de defesa em profundidade e amplificam o impacto de outras falhas (XSS, CSRF, sequestro de sessão).

Severidade: Baixa (habilitadora — eleva a severidade de falhas correlacionadas)

CVSS: 4.3 (AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N) — CVSS 3.1, Baixa

Pré-condições: Acesso de rede à aplicação; para parte dos vetores (clickjacking, SSL-strip, roubo de sessão), interação do usuário e/ou posição de rede privilegiada.

Evidência: Resposta HTTP do servidor sem os cabeçalhos Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Referrer-Policy e Permissions-Policy (capturada via curl -I https://alvo). Cookies de sessão emitidos sem HttpOnly/Secure.

Ajuste o vetor CVSS conforme o que de fato está ausente e o contexto do alvo — esse é um valor de referência razoável para “missing security headers” em uma aplicação exposta na internet.

Como configurar (a linha exata)

Nginx

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=()" always;

O always garante que o cabeçalho vá também nas respostas de erro (4xx/5xx). Para remover a versão do servidor, use server_tokens off;.

Apache

Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set Content-Security-Policy "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=(), payment=(), usb=()"
Header always unset X-Powered-By

Requer o módulo mod_headers. Para esconder a versão, ServerTokens Prod e ServerSignature Off.

Node / Express (helmet)

O atalho aqui é o helmet, que já liga a maioria desses cabeçalhos com defaults sãos numa linha:

const helmet = require("helmet");

app.use(helmet({
  strictTransportSecurity: {
    maxAge: 63072000,
    includeSubDomains: true,
    preload: true,
  },
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      objectSrc: ["'none'"],
      baseUri: ["'self'"],
      frameAncestors: ["'none'"],
    },
  },
  referrerPolicy: { policy: "strict-origin-when-cross-origin" },
}));

app.disable("x-powered-by");

O helmet já manda X-Frame-Options: DENY, X-Content-Type-Options: nosniff e amigos por padrão; você só sobrescreve o que precisar. Note que ele não define Permissions-Policy por padrão — adicione na mão se quiser.

Cloudflare

Se você está atrás da Cloudflare, não precisa tocar no servidor de origem. Use os Managed Transforms → Add security headers (toggles prontos no painel, incluindo HSTS) ou, para controle fino, as Transform Rules → HTTP Response Header Modification, adicionando e removendo cada cabeçalho. É a forma mais rápida de aplicar isso numa frota inteira de domínios sem deploy.

Vale o lembrete: você não precisa montar esses blocos no braço. Cole os headers do seu site no Analisador de Security Headers e ele gera o fix automaticamente para Nginx, Apache, helmet e Cloudflare, já com os valores recomendados.

Checklist de headers de segurança

  • CSP forte com default-src 'self', object-src 'none', base-uri 'self' e nonce/hash em vez de 'unsafe-inline'
  • HSTS com max-age longo, includeSubDomains, preload — e domínio cadastrado na preload list
  • X-Frame-Options: DENY + frame-ancestors 'none' no CSP
  • X-Content-Type-Options: nosniff
  • Referrer-Policy: strict-origin-when-cross-origin (nunca unsafe-url)
  • Permissions-Policy negando câmera, microfone, geolocalização e o que a app não usa
  • COOP (same-origin) e CORP; COEP só se realmente necessário
  • Remover Server com versão, X-Powered-By, X-AspNet-Version
  • Cookies com Secure, HttpOnly e SameSite (Lax por padrão; None só com Secure)
  • Revalidar com uma ferramenta de scoring e garantir que os headers vão também nas respostas de erro

Fechamento

Security headers são o quick win mais óbvio que existe: minutos de configuração que elevam a barra para uma porção de ataques e reduzem o estrago de outros. Mas não se engane — eles são defesa em profundidade, não um substituto para corrigir as falhas de fato. Um CSP impecável não conserta o XSS embaixo dele, só o contém. Se você quer saber o que um atacante realmente faria com a sua aplicação — com ou sem os headers no lugar — fale com a gente. A gente testa de verdade.