Reduza o scripting em vários locais (XSS) com uma Política de Segurança de Conteúdo (CSP) rígida

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

O scripting em vários sites (XSS), a capacidade de injetar scripts maliciosos em um app da Web, é 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 reduzir 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 baseada em nonces ou hashes para mitigar o XSS, em vez das CSPs baseadas em listas de permissões de host usadas com frequência, que geralmente deixam a página exposta ao XSS porque podem ser ignoradas na maioria das configurações.

Termo-chave: um nonce é um número aleatório usado apenas uma vez que você pode usar 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. É possível usar um hash (por exemplo, SHA-256) para marcar uma tag <script> inline como confiável.

Uma Política de Segurança de Conteúdo baseada em nonces ou hashes é geralmente chamada de CSP estrita. Quando um aplicativo usa uma CSP estrita, 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 estrita só permite scripts com hash ou scripts com o valor de 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 estrita?

Se o site já tiver uma CSP parecida com script-src www.googleapis.com, ela provavelmente não será eficaz contra ataques entre sites. Esse tipo de CSP é chamado de CSP de lista de permissões. Eles exigem muita personalização e podem ser ignorados por invasores.

As CSPs estritas baseadas em hashes ou nonces criptográficos evitam essas armadilhas.

Estrutura de CSP rigorosa

Uma política de segurança de conteúdo básica e rigorosa usa um dos seguintes cabeçalhos de resposta HTTP:

CSP rigorosa baseada em nonce

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
Como funciona uma CSP estrita baseada em valor de uso único.

CSP rigorosa baseada em hash

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

As propriedades a seguir tornam uma CSP como esta "estrita" e, portanto, segura:

  • Ela usa nonces 'nonce-{RANDOM}' ou hashes 'sha256-{HASHED_INLINE_SCRIPT}' para indicar quais tags <script> o desenvolvedor do site confia para execução no navegador do usuário.
  • Ele define 'strict-dynamic' para reduzir o esforço de implantação de uma CSP baseada em nonce ou hash, 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.
  • Ela não se baseia em listas de permissões de URL, portanto, não sofre com desvios comuns de 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.
  • Ele restringe base-uri para bloquear a injeção de tags <base>. Isso impede que invasores alterem os locais dos scripts carregados de URLs relativos.

Adotar uma CSP rigorosa

Para adotar uma CSP estrita, você precisa:

  1. Decida se o aplicativo deve definir uma CSP baseada em valor de uso único ou em hash.
  2. Copie a CSP da seção Estrutura de CSP estrita e defina-a como um cabeçalho de resposta em todo o aplicativo.
  3. Refatore os modelos HTML e o código do lado do cliente para remover padrões incompatíveis com a CSP.
  4. Implante sua CSP.

Você pode usar a auditoria de Práticas recomendadas do Lighthouse (v7.3.0 e mais recentes com a flag --preset=experimental) durante todo esse processo para verificar se o site tem uma CSP e se ela é rigorosa o suficiente para ser eficaz contra XSS.

Relatório do Lighthouse
  avisando que nenhuma CSP foi encontrada no modo restrito.
Se o site não tiver uma CSP, o Lighthouse vai mostrar este aviso.

Etapa 1: decidir se você precisa de uma CSP baseada em valor de uso único ou hash

Veja como os dois tipos de CSP estrita funcionam:

CSP baseado em valor de uso único

Com uma CSP baseada em valor de uso único, você gera um número aleatório no ambiente de execução, inclui esse número na CSP e o associa a cada tag script na página. Um invasor não pode incluir ou executar um script malicioso na sua página, porque precisaria adivinhar o número aleatório correto para esse script. Isso só funciona se o número não for adivinhável e for gerado novamente no ambiente de execução para cada resposta.

Use uma CSP baseada em valor de uso único para páginas HTML renderizadas no servidor. Para essas páginas, é possível 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 script inline é adicionado à CSP. Cada script tem um hash diferente. Um invasor não pode incluir ou executar um script malicioso na sua página, porque o hash desse script precisaria estar na sua CSP para ser executado.

Use uma CSP baseada em hash para páginas HTML veiculadas de forma estática ou 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 disponibilizados estaticamente sem renderização do lado do servidor.

Etapa 2: definir uma CSP estrita e preparar os scripts

Ao definir uma CSP, você tem algumas opções:

  • Modo somente relatório (Content-Security-Policy-Report-Only) ou modo restrito (Content-Security-Policy). No modo somente relatório, a CSP ainda não bloqueia recursos, então nada no seu site é interrompido, mas você pode ver erros e receber relatórios sobre tudo o que teria sido bloqueado. Localmente, ao definir sua CSP, isso não importa, porque os dois modos mostram os erros no console do navegador. O modo restrito pode ajudar você a encontrar recursos que o rascunho da CSP bloqueia, 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 o desenvolvimento local, uma tag <meta> pode ser mais conveniente para ajustar sua CSP e ver rapidamente como ela afeta seu site. No entanto:
    • Mais tarde, ao implantar sua 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 um cabeçalho, porque as metatags da CSP não são compatíveis com o modo somente relatório.

Opção A: CSP baseado em valor de uso único

Defina o seguinte cabeçalho de resposta HTTP Content-Security-Policy no aplicativo:

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Gerar um valor de uso único para CSP

Um valor de uso único é um número aleatório usado apenas uma vez por carregamento de página. Uma CSP baseada em nonce só pode mitigar o XSS se os invasores não conseguirem adivinhar o valor do nonce. Um nonce de CSP precisa ser:

  • Um valor aleatório criptograficamente forte (idealmente com mais de 128 bits de comprimento)
  • Gerado novamente para cada resposta
  • Codificação em Base64

Confira alguns exemplos de como adicionar um nonce de CSP em frameworks do lado do servidor:

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 nonce. A primeira etapa é adicionar esses atributos a todos os scripts para que a CSP os permita.

Opção B: cabeçalho de resposta da CSP com base em hash

Defina o seguinte cabeçalho de resposta HTTP Content-Security-Policy no 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

É possível carregar scripts de terceiros de forma dinâmica usando um script inline.

Um exemplo de como incorporar seus scripts.
Permitido pela CSP
<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 inline e adicione-o ao cabeçalho de resposta da CSP, substituindo o marcador de posição {HASHED_INLINE_SCRIPT}. Para reduzir a quantidade de hashes, mescle todos os scripts inline em um único script. Para ver isso em ação, consulte este exemplo e o código dele.
Bloqueado pela CSP
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
A CSP bloqueia esses scripts porque eles não foram adicionados dinamicamente e não têm um atributo integrity que corresponda a uma origem permitida.

Considerações sobre o carregamento de scripts

O exemplo de script inline adiciona s.async = false para garantir que foo seja executado antes de bar, mesmo que bar seja carregado primeiro. Nesse snippet, s.async = false não bloqueia o analisador enquanto os scripts são carregados, porque eles são adicionados dinamicamente. O analisador é interrompido apenas durante a execução dos scripts, como acontece com os scripts async. No entanto, com esse snippet, lembre-se:

  • Um ou ambos os scripts podem ser executados antes que o download do documento seja concluído. Se você quiser que o documento esteja pronto quando os scripts forem executados, aguarde o evento DOMContentLoaded antes de anexar os scripts. Se isso causar um problema de desempenho porque os scripts não começam a ser baixados com antecedência suficiente, use tags de pré-carregamento no início da página.
  • defer = true não faz nada. Se você precisar desse comportamento, execute o script manualmente quando necessário.

Etapa 3: refatorar os modelos HTML e o código do lado do cliente

Manipuladores de eventos inline (como onclick="…", onerror="…") e URIs de JavaScript (<a href="javascript:…">) podem ser usados para executar scripts. Isso significa que um invasor que encontrar um bug de XSS pode injetar esse tipo de HTML e executar JavaScript malicioso. Uma CSP baseada em nonce ou hash proíbe o uso desse tipo de marcação. Se o site usar algum desses padrões, será necessário refatorá-los em alternativas mais seguras.

Se você ativou a CSP na etapa anterior, poderá ver as violações dela no console sempre que ela bloquear um padrão incompatível.

Relatórios de violação da CSP no console para desenvolvedores do Chrome.
Erros do console para código de bloqueio.

Na maioria dos casos, a correção é simples:

Refatorar manipuladores de eventos inline

Permitido pela CSP
<span id="things">A thing.</span>
<script nonce="${nonce}">
  document.getElementById('things').addEventListener('click', doThings);
</script>
A CSP permite manipuladores de eventos registrados usando JavaScript.
Bloqueado pela CSP
<span onclick="doThings();">A thing.</span>
A CSP bloqueia manipuladores de eventos inline.

Refatorar URIs de javascript:

Permitido pela CSP
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
A CSP permite manipuladores de eventos registrados usando JavaScript.
Bloqueado pela CSP
<a href="javascript:linkClicked()">foo</a>
A CSP bloqueia URIs javascript:.

Remova eval() do JavaScript.

Se o aplicativo usa eval() para converter serializações de string 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(), você ainda poderá definir uma CSP estrita baseada em nonce, mas precisará usar a palavra-chave de CSP 'unsafe-eval', o que torna sua política um pouco menos segura.

Você pode encontrar esses e outros exemplos de refatoração neste codelab de CSP restrita:

Etapa 4 (opcional): adicionar substitutos para oferecer suporte a versões antigas de navegadores

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

Se você precisar de suporte para versões mais antigas do navegador:

  • O uso de strict-dynamic requer a adição de https: como substituto para versões anteriores do Safari. Quando você faz isso:
    • Todos os navegadores compatíveis com strict-dynamic ignoram o substituto https:, portanto, isso não reduz a força da política.
    • Em navegadores antigos, os scripts de origem externa só podem ser carregados se forem de uma origem HTTPS. Isso é menos seguro do que uma CSP estrita, mas ainda evita algumas causas comuns de XSS, como injeções de URIs javascript:.
  • Para garantir a compatibilidade com versões muito antigas de navegadores (mais de 4 anos), adicione unsafe-inline como substituto. Todos os navegadores recentes ignoram unsafe-inline se um nonce ou hash de 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 a CSP não bloqueia scripts legítimos no seu ambiente de desenvolvimento local, implante-a no ambiente de preparo e, em seguida, no ambiente de produção:

  1. (Opcional) Implante sua 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 de console e relatórios de violação quando encontra padrões incompatíveis com a CSP. Assim, você pode ver o que teria sido interrompido para os usuários finais. Para mais informações, consulte a API Reporting.
  2. Quando tiver certeza de que a CSP não vai causar problemas no site para os usuários finais, implante-a usando o cabeçalho de resposta Content-Security-Policy. Recomendamos definir a CSP usando um cabeçalho HTTP do lado do servidor, porque é mais seguro do que uma tag <meta>. Depois de concluir essa etapa, sua CSP vai começar a proteger o app contra XSS.

Limitações

Uma CSP estrita geralmente fornece uma camada extra de segurança que ajuda a mitigar XSS. Na maioria dos casos, a CSP reduz significativamente a superfície de ataque, rejeitando padrões perigosos, como URIs javascript:. No entanto, dependendo do tipo de CSP que você está usando (valores de uso único, hashes, com ou sem 'strict-dynamic'), há casos em que a CSP não protege seu app tão bem:

  • Se você usar um nonce em um script, mas houver uma injeção diretamente no corpo ou no parâmetro src desse elemento <script>.
  • Se houver injeções nos locais de scripts criados dinamicamente (document.createElement('script')), incluindo em qualquer função de biblioteca que crie nós DOM script com base nos valores dos argumentos. Isso inclui algumas APIs comuns, como .html() do jQuery, além de .get() e .post() no jQuery < 3.0.
  • Se houver injeções de modelo em aplicativos AngularJS antigos. Um invasor que pode injetar em um modelo do AngularJS pode usá-lo para executar JavaScript arbitrário.
  • Se a política contiver 'unsafe-eval', as injeções em eval(), setTimeout() e algumas outras APIs raramente usadas.

Os desenvolvedores e engenheiros de segurança precisam prestar atenção especial a esses padrões durante as revisões de código e auditorias de segurança. Veja mais detalhes sobre esses casos em Política de segurança de conteúdo: uma confusão bem-sucedida entre proteção e mitigação.

Leitura adicional