Content Security Policy (CSP): como funciona, como testar e como corrigir

O header CSP limita quais scripts uma página pode executar. Diretivas fracas como 'unsafe-inline' ou curingas deixam o XSS sem contenção.

Severidade típica: Média · Verificado pelo módulo da NovaPentest: Content Security Policy Analysis

O que é

Content Security Policy (CSP) é um header de resposta HTTP que diz ao navegador de quais origens uma página pode carregar conteúdo. Cada diretiva cuida de um tipo de recurso: script-src para JavaScript, style-src para CSS, img-src para imagens, connect-src para destinos de fetch e XHR, object-src para plugins e default-src como fallback para toda diretiva de busca que não foi definida explicitamente. Quando a página tenta carregar ou executar algo que a política não permite, o navegador bloqueia e pode reportar a violação.

CSP não substitui escape de saída nem validação de entrada. Ela é uma segunda camada: se alguém conseguir injetar marcação na sua página, uma política bem montada impede que o script injetado rode ou carregue código de um servidor controlado pelo atacante.

O nível de proteção depende do conteúdo da política, não só de ela existir. Algumas expressões desfazem quase todo o benefício sem alarde. 'unsafe-inline' libera qualquer script inline ou event handler, que é justamente o formato da maioria dos payloads injetados. 'unsafe-eval' permite transformar strings em código com eval(), new Function() e parecidos. Um * sozinho ou um curinga amplo como *.exemplo.com.br libera código de hosts que você não controla. E uma política sem object-src ou default-src deixa alguns tipos de recurso sem restrição nenhuma.

Uma política moderna e forte normalmente se apoia em nonces ou hashes: cada script legítimo recebe um token aleatório, gerado a cada resposta, e todo o resto é recusado. A palavra-chave 'strict-dynamic' deixa esses scripts confiáveis carregarem suas próprias dependências, sem você precisar manter uma lista enorme de hosts.

Como é explorada

A CSP faz diferença exatamente quando outra defesa já falhou. Imagine que um campo de comentário, um parâmetro de busca ou um widget de terceiros permite ao atacante colocar HTML em uma das suas páginas. Sem uma política efetiva, essa injeção vira cross-site scripting: o código do atacante roda com os mesmos privilégios do seu JavaScript, na sua origem, no navegador da vítima.

A partir daí o atacante faz tudo o que o seu front-end faz. Pode ler os dados exibidos na tela, disparar chamadas para a sua API usando a sessão da vítima, alterar configurações da conta, capturar o que o usuário digita em formulários ou reescrever a página com uma tela de login falsa bem convincente. Cookies HttpOnly não podem ser lidos diretamente, mas isso pouco importa quando dá para agir de dentro da própria sessão.

Cada fraqueza que o scanner procura corresponde a um jeito de contornar a política. Com 'unsafe-inline' no script-src, uma tag script injetada ou um atributo onerror simplesmente executa, e a política não barra nada. Com 'unsafe-eval', qualquer ponto em que a aplicação transforma string em código vira caminho de execução. Com um curinga, basta hospedar o payload em algum domínio permitido, inclusive domínios de hospedagem compartilhada ou de CDN onde qualquer pessoa pode subir arquivos. Sem object-src, conteúdo de plugin legado fica liberado nos navegadores que ainda o suportam.

'unsafe-inline' no style-src é menos grave, já que CSS sozinho não executa JavaScript, mas estilos injetados ainda servem para desfigurar a página, sobrepor conteúdo falso ou, em alguns casos, vazar dados por seletores que carregam imagens.

Na prática: uma CSP fraca não deixa a página vulnerável por si só, mas tira a rede de proteção. Quando aparece um bug de XSS, e em aplicações grandes ele acaba aparecendo, uma política estrita costuma ser o que separa um report inofensivo de um incidente real.

Como detectar

A verificação Content Security Policy Analysis faz um único GET na URL informada, segue redirecionamentos (cada salto é validado de novo) e lê o header Content-Security-Policy da página final. Assim, o que é avaliado não é um 301 vazio do www para o domínio raiz. Ela roda nos scans completos e também no scanner passivo gratuito em novapentest.com/scan. Existem quatro tipos de apontamento. Qualquer diretiva com 'unsafe-inline' gera um achado de severidade Média, listando as diretivas afetadas; ou seja, style-src conta tanto quanto script-src. Qualquer diretiva com 'unsafe-eval' também é Média. Uma origem que seja só * ou comece com *. gera achado Baixo. Por fim, a ausência de default-src ou object-src é reportada como Baixa; a falta de script-src só é apontada quando default-src também não existe, porque default-src funciona como fallback. Se o header não existir, esta verificação não diz nada e quem reporta a ausência da CSP, com severidade Média, é a verificação separada de HTTP headers.

A verificação tem limites, e vale ser claro sobre eles. Ela lê só o header que é aplicado, não o Content-Security-Policy-Report-Only nem uma política definida em tag meta no HTML. Ela não aponta 'unsafe-inline' quando a mesma diretiva também tem nonce, hash ou 'strict-dynamic', porque os navegadores modernos ignoram o 'unsafe-inline' nessa combinação. Também aponta object-src ausente mesmo quando default-src 'none' já cobre o caso. Não avalia origens por esquema, como https: ou data:, nem diretivas como base-uri e frame-ancestors, e analisa uma única página.

Para conferir na mão, rode curl -sIL https://exemplo.com.br | grep -i content-security-policy e leia o último valor impresso, que é o da resposta final. Veja se script-src ou default-src contém 'unsafe-inline', 'unsafe-eval' ou curinga. Repita para as páginas importantes, porque a política costuma variar por rota, e abra o console do DevTools para ver o que a política realmente bloqueia.

Como corrigir

Corrija por ordem de impacto e publique cada mudança primeiro em modo report-only, para não quebrar o site.

1. Faça o deploy da política candidata como Content-Security-Policy-Report-Only com um endpoint em report-to ou report-uri, navegue pelo site e colete as violações por alguns dias antes de passar a aplicar.

2. Troque o 'unsafe-inline' do script-src por nonces. Gere um valor aleatório novo a cada resposta, envie no header e coloque o mesmo valor em cada tag script legítima. Um bom ponto de partida: Content-Security-Policy: script-src 'nonce-R4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'. Com 'strict-dynamic', scripts carregados por um script confiável também passam a ser confiáveis, então não é preciso manter lista de hosts. Nonce fixo não vale mais que 'unsafe-inline'.

3. Leve os event handlers inline, como atributos onclick, para arquivos de script, porque nonce não se aplica a eles. Para scripts inline pequenos e fixos, um hash sha256 no script-src é alternativa ao nonce.

4. Remova o 'unsafe-eval'. Substitua eval() e new Function() por JSON.parse ou funções explícitas, passe funções e não strings para o setTimeout e descubra quais bibliotecas ainda dependem de eval.

5. Troque curingas por hosts explícitos e defina um fallback restritivo: default-src 'self'; object-src 'none'. Inclua frame-ancestors 'self' para controlar quem pode colocar o site em iframe.

6. Deixe o 'unsafe-inline' do style-src por último; o risco é menor, mas nonces e hashes também funcionam para estilos.

7. Passe a aplicar a política quando os reports estiverem limpos, mantenha o report ligado e envie o header em toda resposta HTML, inclusive páginas de erro. Sem nonce, dá para usar add_header Content-Security-Policy "..." always; no nginx; com nonce, gere o header em um middleware da aplicação.

Verifique o seu próprio site

Comece pelo scan passivo gratuito: não precisa de conta e você recebe uma nota de A a F cobrindo cabeçalhos, TLS, cookies, DNS e outras configurações que qualquer visitante consegue observar. Ele não envia payloads de ataque, então não confirma todas as classes de falha. O scan completo, disponível com uma conta gratuita, executa as verificações ativas, incluindo a descrita nesta página.

Escaneie seu site grátis