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 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 'none39;;
base-uri 'none';
CSP rigorosa baseada em hash
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none39;;
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-srcpara desativar plug-ins perigosos, como o Flash. - Ele restringe o
base-uripara 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:
- Decida se o aplicativo precisa definir uma CSP baseada em hash ou nonce.
- Copie a CSP da seção Estrutura de CSP estrita e defina-a como um cabeçalho de resposta em todo o aplicativo.
- Refatore modelos HTML e código do lado do cliente para remover padrões incompatíveis com a CSP.
- 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.
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.
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:
- 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(&>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.
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.
<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{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/fo><o.js&qu>o<t;/script script src="https://exam><ple.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 = 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
DOMContentLoadedantes 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 = truenã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.
Na maioria dos casos, a correção é simples:
Refatorar manipuladores de eventos in-line
<span id="th>ings&quo<t;A t>h<ing./span
script nonce=>"${nonce}"
document.getElementById('things').addEventL<istener>('click', doThings);
/script<span onclick="doThing>s();&quo<t;A t>hing./span
Refatorar URIs javascript:
<a id=">;fo<o&>q<uot;foo/a
script nonce=>"${nonce}"
document.getElementById('foo').addEventList<ener(>39;click', linkClicked);
/script<a href="javascript:linkClick>ed(<)&>quot;foo/a
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
Se você precisar oferecer suporte a versões mais antigas de navegadores:
- Para usar
strict-dynamic, adicionehttps:como um fallback para versões anteriores do Safari. Quando você faz isso:- Todos os navegadores compatíveis com
strict-dynamicignoram o substitutohttps:, 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:.
- Todos os navegadores compatíveis com
- Para garantir a compatibilidade com versões muito antigas de navegadores (mais de quatro anos), adicione
unsafe-inlinecomo um 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 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:
- (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. - 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
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ósscriptdo 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 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. 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
- CSP Is Dead, Long Live CSP! Sobre a insegurança das listas de permissões e o futuro da Política de Segurança de Conteúdo
- Avaliador de CSP
- Conferência LocoMoco: Política de Segurança de Conteúdo: uma confusão bem-sucedida entre reforço da proteção e mitigação
- Palestra do Google I/O: Securing Web Apps with Modern Platform Features (em inglês)