O scripting em vários locais (XSS), a capacidade de injetar scripts maliciosos em um app da Web, tem sido uma das maiores vulnerabilidades de segurança da Web há mais de uma década.
A Política de Segurança de Conteúdo (CSP)
é uma camada extra de segurança que ajuda a mitigar o XSS. Para configurar uma CSP,
adicione o cabeçalho HTTP Content-Security-Policy
a uma página da Web e defina valores que
controlam quais recursos o user agent pode carregar para essa página.
Nesta página, explicamos como usar uma CSP com base em valores de uso único ou hashes para mitigar XSS, em vez dos CSPs baseados na lista de permissões de host comumente usados, que geralmente deixam a página exposta a XSS porque podem ser ignorados na maioria das configurações.
Termo-chave: um valor de uso único é um número aleatório usado apenas uma vez para marcar uma tag <script>
como confiável.
Termo-chave: uma função hash é uma função matemática que converte um valor
de entrada em um valor numérico compactado chamado hash. Você pode usar um hash
(por exemplo, SHA-256) para marcar uma tag <script>
in-line como confiável.
Uma Política de Segurança de Conteúdo baseada em valores de uso único ou hashes geralmente é chamada de CSP estrita. Quando um aplicativo usa uma CSP rigorosa, os invasores que encontram falhas de injeção de HTML geralmente não podem usá-las para forçar o navegador a executar scripts maliciosos em um documento vulnerável. Isso ocorre porque a CSP rigorosa só permite scripts com hash ou scripts com o valor de uso único correto gerado no servidor. Assim, os invasores não podem executar o script sem saber o valor de uso único correto para uma determinada resposta.
Por que usar uma CSP rigorosa?
Se o site já tiver uma CSP parecida com script-src www.googleapis.com
,
provavelmente não será eficaz em vários sites. Esse tipo de CSP é chamado de
CSP da lista de permissões. Elas exigem muita personalização e podem ser
ignoradas por invasores.
CSPs rigorosas com base em valores de uso único ou hashes criptográficos evitam esses problemas.
Estrutura estrita da CSP
Uma Política de Segurança de Conteúdo estrita básica usa um dos seguintes cabeçalhos de resposta HTTP:
CSP rigorosa com base em não-censura
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
![](https://web.dev/static/articles/strict-csp/image/er4BaGCJzBwDaESFKfZd.jpg?authuser=6&hl=pt)
CSP rigorosa baseada em hash
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
As seguintes propriedades tornam uma CSP como esta "estrita" e, portanto, segura:
- Ele usa
'nonce-{RANDOM}'
de valores de uso único ou hashes'sha256-{HASHED_INLINE_SCRIPT}'
para indicar quais tags de<script>
o desenvolvedor do site confia para executar no navegador do usuário. - Ele define
'strict-dynamic'
para reduzir o esforço de implantar uma CSP baseada em hash ou valor de uso único, permitindo automaticamente a execução de scripts criados por um script confiável. Isso também desbloqueia o uso da maioria das bibliotecas e widgets JavaScript de terceiros. - Ele não é baseado em listas de permissões de URL e, por isso, não apresenta ignorações comuns da CSP.
- Ele bloqueia scripts inline não confiáveis, como manipuladores de eventos inline ou URIs
javascript:
. - Ela restringe o
object-src
para desativar plug-ins perigosos, como o Flash. - Ela restringe
base-uri
para bloquear a injeção de tags<base>
. Isso impede que invasores mudem os locais dos scripts carregados de URLs relativos.
Adote uma CSP rigorosa
Para adotar uma CSP rigorosa, você precisa:
- Decida se o aplicativo deve definir uma CSP com base em hash ou valor de uso único.
- Copie a CSP da seção Estrutura de CSP rigorosa e defina-a como um cabeçalho de resposta em todo o aplicativo.
- Refatore modelos HTML e o código do lado do cliente para remover padrões incompatíveis com a CSP.
- Implante sua CSP.
É possível usar a auditoria de práticas recomendadas do Lighthouse
(v7.3.0 e versões mais recentes com a flag --preset=experimental
)
durante esse processo para verificar se o site tem uma CSP e se ela é
restrita o suficiente para ser eficaz contra XSS.
![Relatório
do Lighthouse informando que nenhuma CSP foi encontrada no modo de aplicação.](https://web.dev/static/articles/strict-csp/image/lighthouse-report-warning-74f66e2ef76dc.png?authuser=6&hl=pt)
Etapa 1: decidir se você precisa de uma CSP com base em hash ou valor de uso único
Veja como os dois tipos de CSPs rigorosas funcionam:
CSP com base em valor de uso único
Com uma CSP baseada em valor de uso único, você gera um número aleatório no tempo de execução, inclui-o na sua CSP e o associa a cada tag de script na sua página. Um invasor não pode incluir ou executar um script malicioso na sua página porque precisa adivinhar o número aleatório correto desse script. Isso só vai funcionar se o número não puder ser adivinhado e for gerado recentemente durante a execução para cada resposta.
Use uma CSP baseada em valor de uso único para páginas HTML renderizadas no servidor. Nessas páginas, você pode criar um novo número aleatório para cada resposta.
CSP baseada em hash
Para uma CSP baseada em hash, o hash de cada tag de script in-line é adicionado à CSP. Cada script tem um hash diferente. Um invasor não pode incluir ou executar um script mal-intencionado na sua página, porque o hash desse script precisaria estar na CSP para que ele seja executado.
Use uma CSP baseada em hash para páginas HTML exibidas estaticamente ou páginas que precisam ser armazenadas em cache. Por exemplo, é possível usar uma CSP baseada em hash para aplicativos da Web de página única criados com frameworks como Angular, React ou outros, que são exibidos estaticamente sem renderização do lado do servidor.
Etapa 2: definir uma CSP rigorosa e preparar os scripts
Ao definir uma CSP, você tem algumas opções:
- Modo somente relatório (
Content-Security-Policy-Report-Only
) ou modo de aplicação (Content-Security-Policy
). Nesse modo, a CSP ainda não bloqueia recursos. Portanto, nada no site é interrompido, mas você pode ver erros e receber relatórios sobre qualquer item que tenha sido bloqueado. Localmente, ao configurar a CSP, isso não importa, porque os dois modos mostram os erros no console do navegador. O modo de aplicação pode ajudar você a encontrar recursos bloqueados pela CSP de rascunho, porque o bloqueio de um recurso pode fazer com que sua página pareça corrompida. O modo somente relatório se torna mais útil posteriormente no processo (consulte a Etapa 5). - Cabeçalho ou tag HTML
<meta>
. Para desenvolvimento local, uma tag<meta>
pode ser mais conveniente para ajustar sua CSP e ver rapidamente como isso afeta seu site. No entanto:- Posteriormente, ao implantar a CSP na produção, recomendamos defini-la como um cabeçalho HTTP.
- Se você quiser definir sua CSP no modo somente relatório, defina-a como cabeçalho, porque as metatags da CSP não são compatíveis com o modo somente relatório.
Defina o seguinte cabeçalho de resposta HTTP
Content-Security-Policy
no seu aplicativo:
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Gerar um valor de uso único para a CSP
Um valor de uso único é um número aleatório usado apenas uma vez por carregamento de página. Uma CSP baseada em valor de uso único só pode mitigar XSS se os invasores não conseguirem adivinhar o valor de uso único. Um valor de uso único da CSP precisa ser:
- Um valor aleatório criptograficamente forte (de preferência com mais de 128 bits)
- Geradas recentemente para cada resposta
- Codificação em Base64
Confira alguns exemplos de como adicionar um valor de uso único da CSP em frameworks do lado do servidor:
- Django (python)
- Expressas (JavaScript):
const app = express(); app.get('/', function(request, response) { // Generate a new random nonce value for every response. const nonce = crypto.randomBytes(16).toString("base64"); // Set the strict nonce-based CSP response header const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`; response.set("Content-Security-Policy", csp); // Every <script> tag in your application should set the `nonce` attribute to this value. response.render(template, { nonce: nonce }); });
Adicione um atributo nonce
aos elementos <script>
.
Com uma CSP baseada em valor de uso único, cada elemento <script>
precisa
ter um atributo nonce
que corresponda ao valor de uso único
aleatório especificado no cabeçalho da CSP. Todos os scripts podem ter o mesmo
valor de uso único. A primeira etapa é adicionar esses atributos a todos os scripts para que a
CSP os permita.
Defina o seguinte cabeçalho de resposta HTTP
Content-Security-Policy
no seu aplicativo:
Content-Security-Policy: script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Para vários scripts inline, a sintaxe é a seguinte:
'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'
.
Carregar scripts de origem dinamicamente
Como hashes da CSP são aceitos em navegadores somente para scripts in-line, é necessário carregar todos os scripts de terceiros dinamicamente usando um script in-line. Os hashes para scripts de origem não têm suporte em todos os navegadores.
![](https://web.dev/static/articles/strict-csp/image/B2YsfJDYw8PRI6kJI7Bs.jpg?authuser=6&hl=pt)
<script> var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js']; scripts.forEach(function(scriptUrl) { var s = document.createElement('script'); s.src = scriptUrl; s.async = false; // to preserve execution order document.head.appendChild(s); }); </script>Para permitir que esse script seja executado, calcule o hash do script in-line e adicione-o ao cabeçalho de resposta da CSP, substituindo o marcador
{HASHED_INLINE_SCRIPT}
. Para reduzir a quantidade de hashes, mescle todos os scripts
in-line em um único script. Para ver isso em ação, consulte este exemplo e o código dele.
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>A CSP bloqueia esses scripts porque somente scripts in-line podem ser criptografados com hash.
Considerações sobre o carregamento de scripts
O exemplo de script in-line adiciona s.async = false
para garantir que foo
seja executado antes de bar
, mesmo que bar
seja carregado primeiro. No snippet a seguir, s.async = false
não bloqueia o analisador enquanto os scripts são carregados, porque eles são
adicionados dinamicamente. O analisador é interrompido apenas enquanto os scripts são executados, como faria para scripts async
. No entanto, com esse snippet,
lembre-se:
-
Um ou ambos os scripts podem ser executados antes da conclusão do download do documento. Se você quiser que o documento esteja pronto quando os scripts forem executados, aguarde o evento
DOMContentLoaded
antes de adicioná-los. Se isso causar um problema de desempenho porque o download dos scripts não é iniciado cedo o suficiente, use as tags de pré-carregamento mais cedo na página. -
O
defer = true
não faz nada. Se precisar desse comportamento, execute o script manualmente, quando necessário.
Etapa 3: refatorar modelos HTML e o código do lado do cliente
É possível usar manipuladores de eventos inline (como onclick="…"
, onerror="…"
) e URIs do JavaScript
(<a href="javascript:…">
) para executar scripts. Isso significa que um
invasor que encontra um bug XSS pode injetar esse tipo de HTML e executar JavaScript
mal-intencionado. Uma CSP com base em hash ou valor de uso único proíbe o uso desse tipo de marcação.
Se o site usar algum desses padrões, será necessário refatorá-los para alternativas mais seguras.
Se você ativou a CSP na etapa anterior, poderá ver as violações da CSP no console sempre que ela bloquear um padrão incompatível.
![Relatórios de violação de CSP no console para desenvolvedores do Chrome.](https://web.dev/static/articles/strict-csp/image/csp-violation-reports-th-ff599b24b17be.jpg?authuser=6&hl=pt)
Na maioria dos casos, a correção é simples:
Refatorar manipuladores de eventos inline
<span id="things">A thing.</span> <script nonce="${nonce}"> document.getElementById('things').addEventListener('click', doThings); </script>A CSP permite manipuladores de eventos registrados com JavaScript.
<span onclick="doThings();">A thing.</span>A CSP bloqueia manipuladores de eventos inline.
Refatorar URIs javascript:
<a id="foo">foo</a> <script nonce="${nonce}"> document.getElementById('foo').addEventListener('click', linkClicked); </script>A CSP permite manipuladores de eventos registrados com JavaScript.
<a href="javascript:linkClicked()">foo</a>A CSP bloqueia URIs do JavaScript.
Remover eval()
do JavaScript
Caso seu aplicativo use eval()
para converter serializações de strings JSON em objetos
JS, refatore essas instâncias para JSON.parse()
, que também é
mais rápido.
Se não for possível remover todos os usos de eval()
, ainda será possível definir uma CSP rigorosa com base no valor de uso único, mas será necessário usar a palavra-chave 'unsafe-eval'
da CSP, o que torna sua política um pouco menos segura.
Confira esses e outros exemplos de refatoração neste codelab de CSP rigorosa:
Etapa 4 (opcional): adicionar substitutos para oferecer suporte a versões antigas do navegador
Se você precisar de suporte para versões mais antigas do navegador:
- O uso de
strict-dynamic
requer a adição dehttps:
como substituto de versões anteriores do Safari. Ao fazer isso:- Todos os navegadores compatíveis com
strict-dynamic
ignoram o substitutohttps:
, então isso não reduzirá a força da política. - Em navegadores antigos, os scripts de origem externa só podem ser carregados se vierem de uma origem HTTPS. Isso é menos seguro do que uma CSP rigorosa, mas ainda
impede algumas causas comuns de XSS, como injeções de URIs
javascript:
.
- Todos os navegadores compatíveis com
- Para garantir a compatibilidade com versões muito antigas do navegador (mais de quatro anos), adicione
unsafe-inline
como substituto. Todos os navegadores recentes ignorarãounsafe-inline
se um valor de uso único ou hash da CSP estiver presente.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
Etapa 5: implantar a CSP
Depois de confirmar que sua CSP não bloqueia scripts legítimos no ambiente de desenvolvimento local, implante sua CSP no preparo e, em seguida, no ambiente de produção:
- (Opcional) Implante a CSP no modo somente relatório usando o cabeçalho
Content-Security-Policy-Report-Only
. O modo somente relatório é útil para testar uma alteração potencialmente interruptiva, como uma nova CSP em produção, antes de começar a aplicar as restrições da CSP. No modo somente relatório, a CSP não afeta o comportamento do app, mas o navegador ainda gera erros do console e relatórios de violação quando encontra padrões incompatíveis com sua CSP. Assim, é possível conferir o que teria corrompido para os usuários finais. Para mais informações, consulte API Reporting. - Quando você tiver certeza de que a CSP não vai corromper seu site para os usuários finais,
implante a CSP usando o cabeçalho de resposta
Content-Security-Policy
. Recomendamos definir sua CSP usando um cabeçalho HTTP do lado do servidor, porque ela é mais segura do que uma tag<meta>
. Depois de concluir essa etapa, a CSP começará a proteger seu app contra XSS.
Limitações
Uma CSP rigorosa geralmente fornece uma camada forte de segurança que ajuda a
reduzir o XSS. Na maioria dos casos, a CSP reduz significativamente a superfície de ataque,
rejeitando padrões perigosos, como URIs javascript:
. No entanto, com base no tipo
de CSP que você está usando (valores de uso único, hashes, com ou sem 'strict-dynamic'
), há
casos em que a CSP também não protege seu app:
- Se você fizer o valor de uso único de um script, mas houver uma injeção diretamente no corpo ou no
parâmetro
src
desse elemento<script>
. - Se houver injeções nos locais dos scripts criados dinamicamente
(
document.createElement('script')
), inclusive em qualquer função de biblioteca que crie nós DOMscript
com base nos valores dos argumentos deles. Isso inclui algumas APIs comuns, como a.html()
do jQuery, assim como.get()
e.post()
no jQuery anterior à versão 3.0. - Se houver injeções de modelo em aplicativos AngularJS antigos. Um invasor que injeta um modelo do AngularJS pode usá-lo para executar JavaScript arbitrário.
- Se a política contiver
'unsafe-eval'
, injeções emeval()
,setTimeout()
e algumas outras APIs usadas raramente.
Os desenvolvedores e engenheiros de segurança precisam prestar atenção especial a esses padrões durante revisões de código e auditorias de segurança. Saiba mais detalhes sobre esses casos em Política de Segurança de Conteúdo: uma confusão entre o aumento da proteção e a mitigação.
Leia mais
- O CSP está morto, viva a CSP! sobre a insegurança das listas de permissões e o futuro da política de segurança de conteúdo
- Avaliador de CSP
- Conferência da LocoMoco: Política de Segurança de Conteúdo - Uma confusão entre o aumento da proteção e a mitigação
- Palestra do Google I/O: como proteger apps da Web com recursos modernos de plataforma