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

Compatibilidade com navegadores

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

Origem

Scripting em vários locais (XSS) a capacidade de injetar scripts maliciosos em um aplicativo da Web, tem sido uma das maiores vulnerabilidades de segurança da Web há mais de uma década.

Política de Segurança de Conteúdo (CSP) é uma camada extra de segurança que ajuda a mitigar ataques de XSS. Para configurar um CSP, adicione o cabeçalho HTTP Content-Security-Policy a uma página da Web e defina valores controlar quais recursos o user agent pode carregar para essa página.

Esta página explica como usar uma CSP baseada em valores de uso único ou hashes para mitigar XSS, em vez dos CSPs com base em lista de permissões de hosts usados com frequência que costumam sair da página expostos 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 que você pode usar para marcar um tag <script> como confiável.

Termo-chave: uma função hash é uma função matemática que converte uma entrada em um valor numérico compactado chamado hash. É possível usar um hash (por exemplo, SHA-256) para marcar um tag <script> como confiável.

Uma Política de Segurança de Conteúdo baseada em valores de uso único ou hashes geralmente é chamada de CSP rigorosa. Quando um aplicativo usa uma CSP rigorosa, os invasores que encontram HTML as falhas de injeção geralmente não podem usá-las para forçar a execução do navegador scripts maliciosos em um documento vulnerável. Isso ocorre porque apenas CSPs rigorosos permite scripts em hash ou scripts com o valor de uso único correto gerado no de modo que os invasores não podem executar o script sem saber o valor de uso único correto. para uma determinada resposta.

Por que você deve usar uma CSP rigorosa?

Caso seu site já tenha uma CSP parecida com script-src www.googleapis.com, ele provavelmente não é eficaz contra sites cruzados. Esse tipo de CSP é chamado de CSP da lista de permissões. Eles exigem muita personalização e podem ser ignorados por invasores.

Os CSPs rígidos baseados em valores de uso único ou hashes criptográficos evitam esses problemas.

Estrutura rígida de CSP

Uma Política básica de Segurança de Conteúdo rigorosa usa uma das seguintes respostas HTTP cabeçalhos:

CSP rigorosa que não se baseia noce CE

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 propriedades a seguir tornam uma CSP como esta "restrita" e, portanto, seguros:

  • Ela usa valores de uso único '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 um CSP com base em hash ou valor de uso único. a execução de scripts criados por um script confiável. Isso também desbloqueia o uso da maioria das bibliotecas e dos widgets JavaScript de terceiros.
  • Ele não é baseado em listas de permissões de URL e, por isso, não sofre ignorações comuns da CSP.
  • Bloqueia scripts inline não confiáveis, como manipuladores de eventos inline ou javascript: URIs.
  • Ele restringe o object-src a desativar plug-ins perigosos como o Flash.
  • Ele restringe o base-uri para bloquear a injeção de tags <base>. Isso evita os invasores de mudar a localização dos scripts carregados de URLs relativos.
.

Adote uma CSP rigorosa

Para adotar uma CSP rigorosa, você precisa:

  1. Decida se o aplicativo deve definir um CSP com base em hash ou valor de uso único.
  2. Copie o CSP da seção Estrutura estrita de CSP e configure-o 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 que estejam incompatíveis com a CSP.
  4. Implante seu CSP.

Você pode usar o Lighthouse. (v7.3.0 e mais recentes com a flag --preset=experimental) Auditoria de Práticas recomendadas ao longo desse processo para verificar se o site tem uma CSP e se está rígidas o suficiente para serem eficazes contra XSS.

Farol
  informar um aviso de que nenhuma CSP foi encontrada no modo de restrição.
Se o site não tiver uma CSP, o Lighthouse vai mostrar esse aviso.

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

Confira como funcionam os dois tipos de CSP rigorosa:

CSP baseado em valor de uso único

Com um CSP baseado em valor de uso único, você gera um número aleatório durante a execução, inclui-o em sua CSP e associá-la a cada tag de script na sua página. Um invasor não podem incluir ou executar um script malicioso em sua página, porque precisam adivinhar o número aleatório correto para aquele script. Isso só funciona se o número não pode ser adivinhado e é gerado no momento da execução para cada resposta.

Use uma CSP com base 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 baseado 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 uma script em sua página, pois o hash desse script precisaria estar em seu e o CSP para que ele seja executado.

Use um CSP baseado em hash para páginas HTML veiculadas estaticamente ou páginas que precisam ser armazenadas em cache. Por exemplo, é possível usar um CSP baseado em hash para aplicativos da Web aplicativos 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 seus scripts

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

  • Modo somente relatórios (Content-Security-Policy-Report-Only) ou modo de aplicação (Content-Security-Policy). No modo somente relatórios, a CSP não bloqueia recursos ainda, então nada em seu site falha, mas você pode ver erros e obter relatórios sobre tudo que seria bloqueado. Localmente, quando você está configurar sua CSP, isso não importa, porque os dois modos mostram a você no console do navegador. Se for o caso, o modo de aplicação ajuda a encontrar recursos que o rascunho da CSP bloqueia, porque o bloqueio de um recurso pode tornar a página parecer 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 ela afeta seu site. No entanto:
    • Depois, ao implantar seu CSP na produção, recomendamos defini-lo como um cabeçalho HTTP.
    • Se você quiser definir sua CSP no modo somente relatório, será necessário defini-la como uma porque as metatags CSP não são compatíveis com o modo somente relatório.

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

Defina a seguinte resposta HTTP Content-Security-Policy cabeçalho em 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. Um valor de uso único A CSP só pode mitigar o XSS se os invasores não conseguirem adivinhar o valor de uso único. Um O valor de uso único da CSP precisa ser:

  • Um valor aleatório com criptografia forte (de preferência, com mais de 128 bits de comprimento)
  • Gerado 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 });
});

Adicionar 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 CSP. Todos os scripts podem ter a mesma valor de uso único. A primeira etapa é adicionar esses atributos a todos os scripts para que o CSP permite que elas.

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

Defina a seguinte resposta HTTP Content-Security-Policy cabeçalho em 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 os hashes de CSP só têm suporte para scripts inline nos navegadores, é preciso carregar todos os scripts de terceiros dinamicamente usando um script in-line. Os hashes de scripts originados não têm suporte em todos os navegadores.

Um exemplo de como inserir scripts inline.
Permitido pelo 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 a execução desse script, calcule o hash dele. e adicione ao cabeçalho de resposta da CSP, substituindo {HASHED_INLINE_SCRIPT} marcador de posição. Para reduzir a quantidade de hashes, é possível mesclar todas as scripts em um único script. Para conferir como isso funciona, consulte este exemplo e o código dele.
Bloqueado pelo CSP
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
A CSP bloqueia esses scripts porque apenas scripts inline podem ser criptografados com hash.

Considerações sobre o carregamento do script

O exemplo de script in-line adiciona s.async = false para garantir que foo executa antes de bar, mesmo que bar é 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 para apenas quando os scripts são executados, pois o que faria para scripts async. No entanto, com esse snippet, lembre-se:

  • Um ou ambos os scripts podem ser executados antes da conclusão do documento o download. Se você quiser que o documento esteja pronto quando o scripts forem executados, aguarde o evento DOMContentLoaded antes você anexa os scripts. Se isso causar um problema de desempenho porque os scripts não iniciarem o download com antecedência suficiente, use as tags de pré-carregamento anteriormente na página.
  • O defer = true não faz nada. Se você precisar 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 JavaScript (<a href="javascript:…">) pode ser usada para executar scripts. Isso significa que o invasor que encontra um bug XSS poderá injetar esse tipo de HTML e executar JavaScript. 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 em configurações mais seguras alternativas.

Se você ativou a CSP na etapa anterior, pode conferir as violações de CSP em console toda vez que o CSP bloqueia um padrão incompatível.

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

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

Refatorar manipuladores de eventos inline

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

Refatorar URIs javascript:

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

Remover eval() do JavaScript

Se o aplicativo usa eval() para converter serializações de string JSON em JS objetos, 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(), defina uma configuração com base em valor de uso único CSP, mas você precisa usar a palavra-chave 'unsafe-eval' da CSP, o que facilita uma política um pouco menos segura.

É possível encontrar esses e outros exemplos de refatoração nesta CSP rigorosa codelab:

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

Compatibilidade com navegadores

  • Chrome: 52.
  • Borda: 79.
  • Firefox: 52.
  • Safari: 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 para uma e outras versões do Safari. Ao fazer 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 vierem de uma origem HTTPS. É menos seguro do que uma CSP rigorosa, mas ainda assim impede algumas causas comuns de XSS, como injeções de URIs javascript:.
  • Para garantir a compatibilidade com versões muito antigas dos navegadores (mais de 4 anos), você pode adicionar unsafe-inline como substituto. Todos os navegadores recentes ignoram unsafe-inline se houver um valor de uso único ou hash da CSP.
.
Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none';
  base-uri 'none';

Etapa 5: implantar o CSP

Depois de confirmar que sua CSP não bloqueia scripts legítimos na sua ambiente de desenvolvimento local, é possível implantar seu CSP no preparo e, em seguida, no ambiente de produção:

  1. (Opcional) Implante seu 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 um novo CSP em produção, antes de e começar a aplicar restrições da CSP. No modo somente relatório, sua CSP não afetam o comportamento do aplicativo, mas o navegador ainda gera erros do console e violações de políticas ao encontrar padrões incompatíveis com sua CSP, para que você possa ver o que pode prejudicar os usuários finais. Para mais informações, consulte API Reporting.
  2. Quando você tiver certeza de que o CSP não prejudicará seu site para os usuários finais, implantar o CSP usando o cabeçalho de resposta Content-Security-Policy. Qa recomendamos configurar sua CSP usando um cabeçalho HTTP no lado do servidor, porque é mais mais segura que uma tag <meta>. Depois que você concluir essa etapa, seu CSP vai começar protegendo seu app de XSS.
.

Limitações

Um CSP estrito geralmente fornece uma forte camada adicional de segurança que ajuda a mitigar ataques de XSS. Na maioria dos casos, o CSP reduz significativamente a superfície de ataque, rejeitar padrões perigosos, como URIs javascript:. No entanto, com base no tipo de CSP que você usa (valores de uso único, hashes, com ou sem 'strict-dynamic'), há são casos em que a CSP não protege o app também:

  • Se você usar um script com valor de uso único, 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 da biblioteca que criam nós DOM script com base nos valores dos argumentos. Isso inclui algumas APIs comuns, como .html() do jQuery, bem como .get() e .post() em jQuery < 3,0.
  • Se houver injeções de modelo em aplicativos antigos do AngularJS. Um invasor que pode injetar em um modelo 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 devem prestar atenção especial a essas em revisões de código e auditorias de segurança. Você pode encontrar mais detalhes esses casos na Política de Segurança de Conteúdo: uma confusão bem-sucedida entre proteção e mitigação (em inglês).

Leitura adicional