Browser Support
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';
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-srcpara desativar plug-ins perigosos, como o Flash. - Ele restringe
base-uripara 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:
- Decida se o aplicativo deve definir uma CSP baseada em valor de uso único ou em hash.
- Copie a CSP da seção Estrutura de CSP estrita e defina-a como um cabeçalho de resposta em todo o aplicativo.
- Refatore os modelos HTML e o código do lado do cliente para remover padrões incompatíveis com a CSP.
- 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.
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.
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:
- Django (Python)
- Express (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
nonce. 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 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.
<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>{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.
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>
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
DOMContentLoadedantes 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 = truenã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.
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><span onclick="doThings();">A thing.</span>
Refatorar URIs de javascript:
<a id="foo">foo</a>
<script nonce="${nonce}">
document.getElementById('foo').addEventListener('click', linkClicked);
</script><a href="javascript:linkClicked()">foo</a>
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
Se você precisar de suporte para versões mais antigas do navegador:
- O uso de
strict-dynamicrequer a adição dehttps:como substituto para versões anteriores do Safari. Quando você faz isso:- Todos os navegadores compatíveis com
strict-dynamicignoram o substitutohttps:, 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:.
- Todos os navegadores compatíveis com
- Para garantir a compatibilidade com versões muito antigas de navegadores (mais de 4 anos), adicione
unsafe-inlinecomo substituto. Todos os navegadores recentes ignoramunsafe-inlinese 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:
- (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. - 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
srcdesse 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 DOMscriptcom 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 emeval(),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
- CSP Is Dead, Long Live CSP! On the Insecurity of Whitelists and the Future of Content Security Policy
- Avaliador de CSP
- Conferência LocoMoco: Política de Segurança de Conteúdo: uma bagunça bem-sucedida entre proteção e mitigação
- Palestra do Google I/O: Como proteger apps da Web com recursos de plataformas modernas