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

Compatibilidade com navegadores

  • 52
  • 79
  • 52
  • 15,4

Origem

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';
Como funciona uma CSP rigorosa com base 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 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:

  1. Decida se o aplicativo deve definir uma CSP com base em hash ou valor de uso único.
  2. Copie a CSP da seção Estrutura de CSP rigorosa e defina-a como um cabeçalho de resposta em todo o aplicativo.
  3. Refatore modelos HTML e o código do lado do cliente para remover padrões incompatíveis com a CSP.
  4. 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.
Caso seu site não tenha uma CSP, o Lighthouse mostrará esse aviso.

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.

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

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:

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.

Opção B: cabeçalho da resposta da CSP com base 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 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.

Exemplo de como inserir seus 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.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.
Bloqueada pela CSP
<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.
Erros do console para código bloqueado.

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 com JavaScript.
Bloqueada pela CSP
<span onclick="doThings();">A thing.</span>
A CSP bloqueia manipuladores de eventos inline.

Refatorar URIs 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 com JavaScript.
Bloqueada pela CSP
<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

Compatibilidade com navegadores

  • 52
  • 79
  • 52
  • 15,4

Origem

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 de versões anteriores do Safari. Ao fazer isso:
    • Todos os navegadores compatíveis com strict-dynamic ignoram o substituto https:, 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:.
  • 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ão unsafe-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:

  1. (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.
  2. 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 DOM script 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 em eval(), 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