O scripting em vários sites (XSS), a capacidade de injetar scripts maliciosos em um app da Web, tem sido uma das maiores vulnerabilidades de segurança da Web por 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
controlem 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 em lista de permissões de host comumente usados, que geralmente saem da página exposta a XSS, porque eles 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 pode ser usado 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 valores de uso único ou hashes é chamada de CSP estrita. Quando um aplicativo usa um CSP rigoroso, os invasores que encontram falhas de injeção de HTML geralmente não podem forçar o navegador a executar scripts maliciosos em um documento vulnerável. Isso ocorre porque o CSP estrito só permite scripts 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 um CSP rígido?
Se o site já tiver um CSP semelhante a script-src www.googleapis.com
,
ele 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.
CSPs rígidos com base em valores de uso único ou hashes criptográficos evitam esses problemas.
Estrutura rígida de CSP
Uma política de segurança de conteúdo rígida básica usa um dos seguintes cabeçalhos de resposta HTTP:
CSP rigorosa baseada em chave de uso único
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 um CSP como este "rígido" e, portanto, seguro:
- Ele usa valores de uso único
'nonce-{RANDOM}'
ou hashes'sha256-{HASHED_INLINE_SCRIPT}'
para indicar quais tags<script>
são confiáveis para o desenvolvedor do site executar no navegador do usuário. - Ele define
'strict-dynamic'
para reduzir o esforço de implantação de um CSP baseado 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 dos widgets JavaScript de terceiros. - Ele não é baseado em listas de permissões de URL e, portanto, não sofre ignorações comuns da CSP.
- Ele bloqueia scripts inline não confiáveis, como manipuladores de eventos inline ou URIs
javascript:
. - 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 que os invasores alterem os locais dos scripts carregados de URLs relativos.
Adotar uma CSP rigorosa
Para adotar um CSP rígido, você precisa:
- Decida se o aplicativo deve definir um CSP com base em hash ou valor de uso único.
- Copie o CSP da seção Estrutura de CSP estrito e defina-o 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 o CSP.
- Implante a CSP.
Você pode usar a auditoria de Práticas recomendadas
do Lighthouse
(v7.3.0 e mais recentes com a flag --preset=experimental
)
durante esse processo para verificar se o site tem um CSP e se ele é
rigoroso o suficiente para ser eficaz contra XSS.
Etapa 1: decidir se você precisa de uma CSP com base em valor de uso único ou em hash
Confira como os dois tipos de CSP rígido 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 momento da execução, o inclui na CSP e o associa a todas as tags de script na página. Um invasor não pode incluir ou executar um script malicioso na sua página, porque ele precisa adivinhar o número aleatório correto para esse script. Isso só funciona se o número não puder ser adivinhado e for gerado no momento da execução para cada resposta.
Use um CSP baseado 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 um CSP baseado em hash, o hash de cada tag de script inline é adicionado ao 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 precisa estar no CSP para que ele seja executado.
Use um CSP baseado em hash para páginas HTML servidas de forma estática ou páginas que precisam ser armazenadas em cache. Por exemplo, é possível usar um CSP baseado 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 um CSP rígido e preparar os scripts
Ao definir um 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, o CSP ainda não bloqueia recursos. Portanto, nada no seu site é interrompido, mas você pode encontrar erros e receber relatórios de tudo o que seria bloqueado. Localmente, quando você configura o CSP, isso não importa muito, porque os dois modos mostram os erros no console do navegador. Na verdade, o modo de aplicação pode ajudar você a encontrar recursos que a CSP de rascunho bloqueia, porque bloquear um recurso pode fazer com que sua página pareça corrompida. O modo somente para relatórios se torna mais útil mais tarde no processo (consulte a Etapa 5). - Cabeçalho ou tag HTML
<meta>
. Para desenvolvimento local, uma tag<meta>
pode ser mais conveniente para ajustar seu CSP e ver rapidamente como ele afeta seu site. No entanto:- Mais tarde, ao implantar o CSP na produção, recomendamos configurá-lo como um cabeçalho HTTP.
- Se você quiser definir o CSP no modo somente relatório, será necessário defini-lo como um cabeçalho, porque as metatags do CSP não oferecem suporte a 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 a CSP
Um valor de uso único é um número aleatório usado apenas uma vez por carregamento de página. Um CSP baseado 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 do CSP precisa ser:
- Um valor aleatório criptograficamente seguro (de preferência com mais de 128 bits de comprimento)
- Gerado novamente 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:
- Django (python)
- Expresso (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 }); });
Adicionar um atributo nonce
aos elementos <script>
Com um CSP baseado em nonce, cada elemento <script>
precisa
ter um atributo nonce
que corresponda ao valor de nonce aleatório
especificado no cabeçalho do CSP. Todos os scripts podem ter o mesmo
valor de uso único. A primeira etapa é adicionar esses atributos a todos os scripts para que o
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.head.appendChild(s); }); </script>
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>
Considerações sobre o carregamento do script
O exemplo de script inline adiciona s.async = false
para 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ó é interrompido enquanto os scripts são executados, como
acontece com os scripts async
. No entanto, com este snippet,
tenha em mente:
-
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 anexar os scripts. Se isso causar um problema de desempenho porque os scripts não começam a ser transferidos com antecedência suficiente, use tags de pré-carregamento mais cedo na página. -
defer = true
nã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
Os manipuladores de eventos inline (como onclick="…"
, onerror="…"
) e os URIs do JavaScript
(<a href="javascript:…">
) podem ser usados para executar scripts. Isso significa que um
invasor que encontra um bug de XSS pode injetar esse tipo de HTML e executar JavaScript
malicioso. 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 alternativas
mais seguras.
Se você tiver ativado o CSP na etapa anterior, vai ser possível conferir as violações do CSP no console sempre que ele 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 javascript:
<a id="foo">foo</a> <script nonce="${nonce}"> document.getElementById('foo').addEventListener('click', linkClicked); </script>
<a href="javascript:linkClicked()">foo</a>
Remover eval()
do JavaScript
Caso o 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 rígida
baseada em nonce, mas será necessário usar a palavra-chave CSP 'unsafe-eval'
, o que torna a
política um pouco menos segura.
Você pode encontrar esses e mais exemplos de refatoração neste codelab rigoroso da CSP:
Etapa 4 (opcional): adicionar substitutos para oferecer suporte a versões antigas do navegador
Se você precisar de suporte para versões mais antigas do navegador:
- O uso de
strict-dynamic
requer a adição dehttps:
como substituto para versões anteriores do Safari. Ao fazer isso:- Todos os navegadores compatíveis com
strict-dynamic
ignoram 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
virem 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:
.
- Todos os navegadores compatíveis com
- Para garantir a compatibilidade com versões muito antigas do navegador (mais de quatro anos), é possível adicionar
unsafe-inline
como substituto. Todos os navegadores recentes ignoramunsafe-inline
quando há 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 o CSP não bloqueia scripts legítimos no ambiente de desenvolvimento local, você pode implantar o CSP no ambiente de preparo e, em seguida, no ambiente de produção:
- (Opcional) Implante o CSP no modo somente para relatórios usando o
cabeçalho
Content-Security-Policy-Report-Only
. O modo somente relatório é útil para testar uma mudança potencialmente crítica, como um novo CSP em produção, antes de começar a aplicar as restrições do 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 conferir o que teria sido quebrado para os usuários finais. Para mais informações, consulte API Reporting. - Quando você tiver certeza de que o CSP não vai quebrar o site para os usuários finais,
implante o CSP usando o cabeçalho de resposta
Content-Security-Policy
. Recomendamos configurar o CSP usando um cabeçalho HTTP no servidor porque ele é mais seguro do que uma tag<meta>
. Depois de concluir essa etapa, o CSP vai começar a proteger seu app contra XSS.
Limitações
Uma CSP rígida geralmente oferece 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, com base no tipo
de CSP que você está usando (nonces, hashes, com ou sem 'strict-dynamic'
), há
casos em que a CSP não protege o app:
- 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 de scripts criados dinamicamente
(
document.createElement('script')
), incluindo em todas as funções de biblioteca que criam nós DOMscript
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 antigos do AngularJS. 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 revisões de código e auditorias de segurança. Confira 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! Sobre a falta de segurança de listas de permissões e o futuro da Política de Segurança de Conteúdo
- Avaliador de CSP
- Conferência LocaloMoco: política de segurança de conteúdo — uma confusão bem-sucedida entre proteção e mitigação
- Palestra do Google I/O: como proteger apps da Web com recursos modernos da plataforma