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 valores de uso único ou hashes para mitigar o XSS, em vez das CSPs baseadas em lista de permissões de host, que são usadas com frequência e muitas vezes deixam a página exposta ao XSS porque podem ser ignoradas na maioria das configurações.

Termo principal: um nonce é um número aleatório usado apenas uma vez que pode ser usado para marcar uma tag <script> como confiável.

Termo principal: 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> 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 acontece porque a CSP estrita permite apenas scripts com hash ou com o valor de nonce correto gerado no servidor. Assim, os invasores não podem executar o script sem saber o nonce 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. Elas exigem muita personalização e podem ser ignoradas por invasores.

As CSPs estritas baseadas em nonces ou hashes criptográficos evitam esses problemas.

Estrutura de CSP rigorosa

Uma política de segurança de conteúdo estrita básica 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&#39;;
  base-uri 'none';
Como funciona uma CSP estrita baseada em nonce.

CSP rigorosa baseada em hash

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

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

  • Ele usa nonces 'nonce-{RANDOM}' ou hashes 'sha256-{HASHED_INLINE_SCRIPT}' para indicar em quais tags <script> o desenvolvedor do site confia para executar no navegador do usuário.
  • Ele define 'strict-dynamic' para reduzir o esforço de implantação de uma CSP baseada em hash ou nonce, 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 é baseada em listas de permissão de URL, portanto, não sofre de bypasses comuns de CSP.
  • Ele bloqueia scripts inline não confiáveis, como manipuladores de eventos inline ou URIs javascript:.
  • Ele restringe o object-src para desativar plug-ins perigosos, como o Flash.
  • Ele restringe o 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 estrita

Para adotar uma CSP estrita, você precisa:

  1. Decida se o aplicativo precisa definir uma CSP baseada em hash ou nonce.
  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 modelos HTML e código do lado do cliente para remover padrões incompatíveis com a CSP.
  4. Implante a CSP.

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

O relatório do Lighthouse
  avisa que nenhuma CSP foi encontrada no modo restrito.
Se o site não tiver uma CSP, o Lighthouse vai mostrar esse aviso.

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

Confira como os dois tipos de CSP estrita funcionam:

CSP baseada em nonce

Com uma CSP baseada em valor de uso único, você gera um número aleatório em tempo 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 nem 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 previsível e for gerado recentemente no tempo de execução para cada resposta.

Use uma CSP baseada em nonce para páginas HTML renderizadas no servidor. Para essas páginas, você pode criar um novo número aleatório para cada resposta.

CSP com base 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 nem 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 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 servidos de forma estática sem renderização do lado do servidor.

Etapa 2: definir uma CSP estrita e preparar seus 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). No modo somente relatório, a CSP ainda não bloqueia recursos. Portanto, nada no seu site é interrompido, mas você pode ver erros e receber relatórios de tudo que teria sido bloqueado. Localmente, quando você está definindo sua CSP, isso não importa muito, 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 provisória, porque o bloqueio de um recurso pode fazer com que sua página pareça quebrada. O modo somente relatório é mais útil mais tarde no processo (consulte 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 ela afeta seu site. No entanto:
    • Mais tarde, ao implantar sua CSP na produção, recomendamos defini-la como um cabeçalho HTTP.
    • Se quiser definir sua CSP no modo somente relatório, defina como um cabeçalho, porque as metatags da CSP não aceitam esse modo.

Opção A: CSP baseada em nonce

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 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(&>quot;Content-Security-Policy", csp);

  // Every script tag in your application should set the `nonce` attribute to this value.
  response.render(template, { nonce: nonce });
});

Adicionar um atributo nonce aos elementos <script>

Com uma CSP baseada em nonce, todo elemento <script> precisa ter um atributo nonce que corresponda ao valor de nonce 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 baseada em hash

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 in-line, 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 dinamicamente usando um script inline.

Um exemplo de como inserir scripts inline.
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.hea<d.appen>dChild(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/fo><o.js&qu>o<t;/script
script src="https://exam><ple.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 = falsepara garantir que foo seja executado antes de bar, mesmo que bar seja carregado primeiro. Neste snippet, s.async = false não bloqueia o analisador enquanto os scripts são carregados, porque eles são adicionados dinamicamente. O analisador só para enquanto os scripts são executados, como aconteceria com scripts async. No entanto, com este snippet, lembre-se de que:

  • Um ou os dois scripts podem ser executados antes que o documento termine de fazer o download. 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 cedo o suficiente, use tags de pré-carregamento antes na página.
  • defer = true não faz nada. Se você precisar desse comportamento, execute o script manualmente quando necessário.

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

Os manipuladores de eventos inline (como onclick="…" e onerror="…") e os URIs 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 no console sempre que a CSP bloquear um padrão incompatível.

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

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

Refatorar manipuladores de eventos in-line

Permitido pela CSP
<span id="th>ings&quo<t;A t>h<ing./span
script nonce=>"${nonce}"
  document.getElementById('things').addEventL<istener>('click', doThings);
/script
A CSP permite manipuladores de eventos registrados usando JavaScript.
Bloqueado pela CSP
<span onclick="doThing>s();&quo<t;A t>hing./span
A CSP bloqueia manipuladores de eventos inline.

Refatorar URIs javascript:

Permitido pela CSP
<a id=">;fo<o&>q<uot;foo/a
script nonce=>"${nonce}"
  document.getElementById('foo').addEventList<ener(&#>39;click', linkClicked);
/script
A CSP permite manipuladores de eventos registrados usando JavaScript.
Bloqueado pela CSP
<a href="javascript:linkClick>ed(<)&>quot;foo/a
A CSP bloqueia URIs javascript:.

Remover eval() do JavaScript

Se o aplicativo usa 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 estrita baseada em nonce, mas será necessário usar a palavra-chave 'unsafe-eval' da CSP, o que torna sua política um pouco menos segura.

Você encontra esses e outros exemplos de refatoração neste codelab de CSP estrito:

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 oferecer suporte a versões mais antigas de navegadores:

  • Para usar strict-dynamic, adicione https: como um fallback para versões anteriores do Safari. Quando você faz isso:
    • Todos os navegadores compatíveis com strict-dynamic ignoram o substituto https:, então isso não reduz a força da política.
    • Em navegadores antigos, scripts de origem externa só podem ser carregados se vierem de uma origem HTTPS. Isso é menos seguro do que uma CSP estrita, mas ainda impede algumas causas comuns de XSS, como injeções de URIs javascript:.
  • Para garantir a compatibilidade com versões muito antigas de navegadores (mais de quatro anos), adicione unsafe-inline como um 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 9;none';
  base-uri 'none';

Etapa 5: implante sua CSP

Depois de confirmar que sua CSP não bloqueia nenhum script legítimo no ambiente de desenvolvimento local, implante a CSP 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 mudança potencialmente destrutiva, como uma nova CSP em produção, antes de começar a aplicar restrições de CSP. No modo somente relatório, sua 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 prejudicar o site para os usuários finais, implante a CSP 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 oferece uma camada extra de segurança que ajuda a mitigar o 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 (nonces, 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 script do DOM 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', 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. Para mais detalhes sobre esses casos, consulte Política de Segurança de Conteúdo: A Successful Mess Between Hardening and Mitigation (em inglês).

Leitura adicional