O modelo de segurança da Web está enraizado na
política de mesma origem. O código
de https://mybank.com
precisa ter acesso apenas aos dados de https://mybank.com
, e o acesso de https://evil.example.com
não é permitido.
Cada origem é mantida isolada do restante da Web, oferecendo aos desenvolvedores um sandbox
seguro para criar e se divertir. Na teoria, isso é perfeitamente brilhante. Na
prática, os invasores encontraram maneiras inteligentes de subverter o sistema.
Ataques de scripting em vários sites (XSS), por exemplo, ignoram a mesma política de origem, enganando um site para que ele forneça código malicioso com o conteúdo pretendido. Isso é um grande problema, já que os navegadores confiam que todo o código que aparece em uma página é parte legítima da origem de segurança da página. A Folha de referência de XSS é uma seção antiga, mas representativa, dos métodos que um invasor pode usar para violar essa confiança injetando código malicioso. Se um invasor injetar qualquer código, será praticamente o fim do jogo: os dados da sessão do usuário serão comprometidos e as informações que precisam ser mantidas em segredo são exfiltradas para os indícios. É claro que gostaríamos de evitar isso, se possível.
Nesta visão geral, destacamos uma defesa que pode reduzir significativamente o risco e o impacto de ataques XSS em navegadores modernos: a Política de Segurança de Conteúdo (CSP).
Texto longo, leia o resumo
- Use listas de permissões para informar ao cliente o que é permitido ou não.
- Saiba quais diretivas estão disponíveis.
- Conheça as palavras-chave que usam.
- Código embutido e
eval()
são considerados prejudiciais. - Denuncie violações de política ao seu servidor antes de aplicá-las.
Listas de permissões de origem
O problema explorado por ataques XSS é a incapacidade do navegador de distinguir entre um script que faz parte do seu aplicativo e um script que foi injetado maliciosamente por um terceiro. Por exemplo, o Botão +1 do Google na parte inferior desta página carrega e executa o código de https://apis.google.com/js/plusone.js
no contexto da origem desta página. Confiamos nesse código, mas não podemos esperar que o navegador descubra por conta própria que o código de apis.google.com
é incrível, enquanto o código de apis.evil.example.com
provavelmente não é. O navegador faz o download e executa alegremente todos os códigos solicitados por uma página, independentemente da fonte.
Em vez de confiar cegamente em tudo que um servidor fornece, a CSP define o cabeçalho HTTP Content-Security-Policy
, que permite criar uma lista de permissões de fontes de conteúdo confiável e instrui o navegador a executar ou renderizar recursos apenas dessas fontes. Mesmo que um invasor consiga encontrar uma brecha para
injetar o script, ele não corresponderá à lista de permissões e, portanto, não será
executado.
Como confiamos em apis.google.com
para fornecer código válido e em nós mesmos
para fazer o mesmo, vamos definir uma política que só permita que o script seja executado quando
vem de uma dessas duas origens:
Content-Security-Policy: script-src 'self' https://apis.google.com
Simples, certo? Como você deve imaginar, script-src
é uma diretiva que controla um conjunto de privilégios relacionados a scripts para uma página específica. Especificamos
'self'
como uma fonte válida de script e https://apis.google.com
como
outra. Obedientemente, o navegador faz o download e executa JavaScript de
apis.google.com
por HTTPS, bem como a partir da origem da página atual.
Com essa política definida, o navegador simplesmente gera um erro, em vez de carregar um script de qualquer outra origem. Quando um invasor inteligente consegue injetar código no seu site, ele recebe uma mensagem de erro e não o sucesso que esperava.
A política se aplica a uma ampla variedade de recursos
Embora os recursos de script sejam os riscos de segurança mais óbvios, a CSP oferece um conjunto avançado de diretivas de política que permitem um controle bastante granular sobre os recursos que uma página pode carregar. Como você já conheceu script-src
, o conceito
precisa estar claro.
Vamos examinar rapidamente o restante das diretivas de recurso. A lista abaixo representa o estado das diretivas a partir do nível 2. Uma especificação de nível 3 foi publicada, mas ainda não foi implementada nos principais navegadores.
base-uri
restringe os URLs que podem aparecer no elemento<base>
de uma página.child-src
lista os URLs dos workers e do conteúdo do frame incorporado. Por exemplo:child-src https://youtube.com
permitiria a incorporação de vídeos do YouTube, mas não de outras origens.connect-src
limita as origens às quais você pode se conectar (via XHR, WebSockets e EventSource).font-src
especifica as origens que podem disponibilizar fontes da Web. As fontes da Web do Google podem ser ativadas porfont-src https://themes.googleusercontent.com
.form-action
lista endpoints válidos para envio de tags<form>
.frame-ancestors
especifica as origens que podem incorporar a página atual. Essa diretiva se aplica às tags<frame>
,<iframe>
,<embed>
e<applet>
. Essa diretiva não pode ser usada em tags<meta>
e se aplica somente a recursos que não são HTML.- O
frame-src
foi descontinuado no nível 2, mas foi restaurado no nível 3. Se não for presente, ele ainda volta parachild-src
como antes. img-src
define as origens de onde as imagens podem ser carregadas.- O
media-src
restringe as origens com permissão para entregar vídeo e áudio. object-src
permite o controle do Flash e de outros plug-ins.- O
plugin-types
limita os tipos de plug-ins que uma página pode invocar. report-uri
especifica um URL para o qual um navegador enviará relatórios quando uma Política de segurança de conteúdo for violada. Essa diretiva não pode ser usada em tags<meta>
.style-src
é a versão descript-src
das folhas de estilo.- O
upgrade-insecure-requests
instrui os user agents a reescrever os esquemas de URL, mudando o HTTP para HTTPS. Essa diretiva é para sites com um grande número de URLs antigos que precisam ser regravados. worker-src
é uma diretiva de nível 3 da CSP que restringe os URLs que podem ser carregados como um worker, worker compartilhado ou service worker. Desde julho de 2017, essa diretiva tem implementações limitadas (link em inglês).
Por padrão, as diretivas são esparsas. Se você não definir uma política específica para uma
diretiva, como font-src
, ela se comportará por padrão como
se você tivesse especificado *
como a fonte válida. Por exemplo, é possível carregar fontes de
qualquer lugar, sem restrições.
É possível substituir esse comportamento padrão especificando uma diretiva
default-src
. Essa diretiva define os padrões para a maioria
das diretivas que você não especifica. Geralmente, isso se aplica a qualquer diretiva que
termine com -src
. Se default-src
for definido como https://example.com
e você não
especificar uma diretiva font-src
, será possível carregar fontes de
https://example.com
e de nenhum outro lugar. Especificamos apenas script-src
nos
exemplos anteriores, o que significa que imagens, fontes e assim por diante podem ser carregados de
qualquer origem.
As diretivas a seguir não usam default-src
como substituto. Lembre-se de que
não defini-las é o mesmo que permitir qualquer coisa.
base-uri
form-action
frame-ancestors
plugin-types
report-uri
sandbox
Use quantas diretivas você quiser, conforme necessário para seu aplicativo. Basta listar cada uma no cabeçalho HTTP e separá-las por ponto e vírgula. Certifique-se de listar todos os recursos necessários de um tipo específico em uma única diretiva. Se você escreveu algo como script-src https://host1.com; script-src https://host2.com
, a segunda diretiva seria simplesmente ignorada. O exemplo a seguir especificaria corretamente as duas origens como válidas:
script-src https://host1.com https://host2.com
Se, por exemplo, você tiver um aplicativo que carrega todos os recursos de uma
rede de fornecimento de conteúdo (por exemplo, https://cdn.example.net
) e souber que você
não precisa de nenhum conteúdo em frame ou plug-ins, sua política poderá ser semelhante
a esta:
Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'
Detalhes da implementação
Você verá os cabeçalhos X-WebKit-CSP
e X-Content-Security-Policy
em vários tutoriais na Web. Daqui para frente, você deve ignorar esses cabeçalhos
prefixados. Os navegadores modernos (exceto o IE) são compatíveis com o cabeçalho Content-Security-Policy
sem prefixo. Esse é o cabeçalho que você deve usar.
Independentemente do cabeçalho usado, a política é definida página por página: será necessário enviar o cabeçalho HTTP junto com todas as respostas que você quer garantir que sejam protegidas. Isso oferece muita flexibilidade, já que você pode ajustar a política para páginas específicas com base nas necessidades específicas. Talvez um conjunto de páginas em seu site tenha um botão +1, enquanto outras não: você pode permitir que o código do botão seja carregado somente quando necessário.
A lista de fontes em cada diretiva é flexível. É possível especificar origens por
esquema (data:
, https:
) ou variando de apenas nome do host
(example.com
, que corresponde a qualquer origem nesse host: qualquer esquema, qualquer porta) a
um URI totalmente qualificado (https://example.com:443
, que corresponde apenas HTTPS, somente
example.com
e somente à porta 443). Caracteres curinga são aceitos, mas apenas como um esquema,
uma porta ou na posição mais à esquerda do nome do host: *://*.example.com:*
corresponderia
a todos os subdomínios de example.com
(mas não ao próprio example.com
), usando
qualquer esquema, em qualquer porta.
A lista de fontes também aceita quatro palavras-chave:
- Como é de se esperar,
'none'
não corresponde a nada. 'self'
corresponde à origem atual, mas não aos subdomínios dela.'unsafe-inline'
permite JavaScript e CSS inline. Falaremos sobre isso com mais detalhes em breve.- O
'unsafe-eval'
permite mecanismos de texto para JavaScript, comoeval
. (Vamos falar sobre isso também).
Essas palavras-chave exigem aspas simples. Por exemplo, script-src 'self'
(entre aspas)
autoriza a execução de JavaScript no host atual. script-src self
(sem aspas) permite JavaScript de um servidor chamado "self
" (e não do
host atual), o que provavelmente não é o que você quis dizer.
Sandbox
Há mais uma diretiva de que vale a pena falar: sandbox
. Ele é um pouco
diferente dos outros que vimos, já que restringe as ações que
a página pode realizar, e não os recursos que ela pode carregar. Se a diretiva sandbox
estiver presente, a página será tratada como se tivesse sido carregada dentro de um <iframe>
com um atributo sandbox
. Isso pode causar diversos
efeitos na página: forçar a página a ter uma origem exclusiva, impedir o envio
de formulários, entre outros. Isso está um pouco além do escopo deste artigo, mas é possível encontrar todos os detalhes sobre os atributos de sandbox válidos na seção "Sandbox" das especificações do HTML5.
A metatag
O mecanismo de entrega preferido dos CSPs é um cabeçalho HTTP. No entanto, pode ser útil
definir uma política em uma página diretamente na marcação. Para isso, use uma tag <meta>
com
um atributo http-equiv
:
<meta
http-equiv="Content-Security-Policy"
content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'"
/>
Esse recurso não pode ser usado para frame-ancestors
, report-uri
ou sandbox
.
Código embutido é considerado nocivo
É importante deixar claro que a CSP é baseada em origens da lista de permissões, já que essa é uma
maneira inequívoca de instruir o navegador a tratar conjuntos específicos de recursos
como aceitáveis e rejeitar o restante. No entanto, as listas de permissões baseadas na origem não resolvem a maior ameaça representada por ataques XSS: a injeção de script inline.
Se um invasor conseguir injetar uma tag de script que contenha diretamente algum payload
malicioso (<script>sendMyDataToEvilDotCom();</script>
),
o navegador não terá um mecanismo para distingui-lo de uma tag de script inline
legítima. A CSP resolve esse problema banindo totalmente o script in-line.
É a única maneira de ter certeza.
Essa proibição inclui não apenas scripts incorporados diretamente em tags script
, mas também
manipuladores de eventos inline e URLs javascript:
. Você precisará mover o conteúdo das
tags script
para um arquivo externo e substituir URLs javascript:
e <a ... onclick="[JAVASCRIPT]">
pelas chamadas addEventListener()
adequadas. Por exemplo,
é possível reescrever o seguinte:
<script>
function doAmazingThings() {
alert('YOU AM AMAZING!');
}
</script>
<button onclick="doAmazingThings();">Am I amazing?</button>
para algo mais como:
<!-- amazing.html -->
<script src="amazing.js"></script>
<button id="amazing">Am I amazing?</button>
<div style="clear:both;"></div>
// amazing.js
function doAmazingThings() {
alert('YOU AM AMAZING!');
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('amazing').addEventListener('click', doAmazingThings);
});
O código reescrito tem uma série de vantagens além de funcionar bem com a CSP. Essa já é a prática recomendada, independente do uso da CSP. O JavaScript inline mistura estrutura e comportamento exatamente da maneira que você não deveria. Os recursos externos são mais fáceis de armazenar em cache pelos navegadores, mais compreensíveis para os desenvolvedores e propícios para compilação e minificação. Você escreverá um código melhor se fizer o trabalho de movê-lo para recursos externos.
O estilo in-line é tratado da mesma maneira: o atributo style
e as tags style
precisam ser consolidados em folhas de estilo externas para se proteger contra uma variedade de métodos de exfiltração de dados surpreendentemente inteligentes que o CSS permite.
Se você precisa ter um script e estilo in-line, ative-os
adicionando 'unsafe-inline'
como uma origem permitida em uma diretiva
script-src
ou style-src
. Você também pode usar um valor de uso único ou um hash (confira abaixo), o que não é recomendável.
Banir script in-line é a maior vantagem de segurança que a CSP oferece, e fazer isso também aumenta a proteção do seu aplicativo. É necessário um pouco de esforço no início para garantir que tudo funcione corretamente depois de mover todo o código para fora da linha, mas é uma troca que vale a pena tomar.
Se for realmente necessário usá-lo,
A CSP de nível 2 oferece compatibilidade com versões anteriores para scripts inline, permitindo que você adicione scripts in-line específicos à lista de permissões usando um valor de uso único criptográfico (número usado uma vez) ou um hash. Embora possa ser complicado, é útil como alternativa.
Para usar um valor de uso único, atribua um atributo de valor de uso único à tag de script. Seu valor precisa corresponder a um na lista de fontes confiáveis. Exemplo:
<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
// Some inline code I can't remove yet, but need to asap.
</script>
Agora, adicione o valor de uso único à diretiva script-src
anexada à palavra-chave nonce-
.
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
Lembre-se de que os valores de uso único precisam ser gerados novamente para cada solicitação de página e não podem ser adivinhados.
Os hashes funcionam da mesma forma. Em vez de adicionar o código à tag do script, crie um hash SHA do próprio script e o adicione à diretiva script-src
.
Por exemplo, digamos que sua página tenha o seguinte conteúdo:
<script>
alert('Hello, world.');
</script>
Sua política conteria o seguinte:
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
Há algumas coisas a serem observadas aqui. O prefixo sha*-
especifica o algoritmo
que gera o hash. No exemplo acima, usamos sha256-
. A CSP também oferece suporte a sha384-
e sha512-
. Ao gerar o hash, não inclua as
tags <script>
. Além disso, letras maiúsculas e minúsculas e espaços em branco são importantes, incluindo
espaços em branco no início ou no fim.
Uma pesquisa no Google sobre como gerar hashes SHA levará você a soluções em vários idiomas. Usando o Chrome 40 ou posterior, você pode abrir o DevTools e atualizar a página. A guia "Console" vai conter mensagens de erro com o hash sha256 correto para cada um dos scripts inline.
Eval também
Mesmo quando um invasor não consegue injetar um script diretamente, ele pode induzir seu aplicativo
a converter um texto inerte em JavaScript executável
e executá-lo em nome dele. eval()
, as novas
Function(), setTimeout([string], ...)
e
setInterval([string], ...)
são vetores pelos quais o texto
injetado pode acabar executando algo inesperadamente malicioso. A resposta padrão do CSP
a esse risco é bloquear completamente todos esses vetores.
Isso causa alguns impactos na maneira como você cria aplicativos:
- Você precisa analisar o JSON usando o
JSON.parse
integrado em vez de depender deeval
. As operações JSON nativas estão disponíveis em todos os navegadores desde o IE8 e são completamente seguras. - Reescreva todas as chamadas
setTimeout
ousetInterval
que você está fazendo no momento com funções inline em vez de strings. Exemplo:
setTimeout("document.querySelector('a').style.display = 'none';", 10);
seria melhor escrito como:
setTimeout(function () {
document.querySelector('a').style.display = 'none';
}, 10);
- Evite modelos inline durante a execução: muitas bibliotecas de modelos usam
new Function()
abundantemente para acelerar a geração de modelos no momento da execução. É uma aplicação inteligente de programação dinâmica, mas tem o risco de avaliar texto malicioso. Alguns frameworks oferecem suporte à CSP pronta para uso, recorrendo a um analisador robusto na ausência deeval
. A diretiva ng-csp do AngularJS é um bom exemplo disso.
No entanto, uma escolha melhor seria uma linguagem de modelo que ofereça
pré-compilação (por exemplo, o Handlebars oferece). A pré-compilação dos modelos pode tornar a experiência do usuário ainda
mais rápida do que a implementação de ambiente de execução mais rápida, além de ser mais segura. Se a avaliação e
a biblioteca correspondente de texto para JavaScript forem essenciais para seu aplicativo, será possível
ativá-las adicionando 'unsafe-eval'
como uma fonte permitida em uma diretiva script-src
.
No entanto, não é recomendável fazer isso. Banir a capacidade de executar strings dificulta muito a execução de código não autorizado no seu site.
Relatório
A capacidade do CSP de bloquear recursos não confiáveis no lado do cliente é uma grande vantagem para seus
usuários, mas seria muito útil ter algum tipo de notificação
enviado ao servidor para que você possa identificar e eliminar bugs que permitam
injeção maliciosa. Para isso, você pode instruir o navegador a POST
relatórios de violação formatados em JSON para um local especificado em uma diretiva report-uri
.
Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
Esses relatórios serão semelhantes a estes:
{
"csp-report": {
"document-uri": "http://example.org/page.html",
"referrer": "http://evil.example.com/",
"blocked-uri": "http://evil.example.com/evil.js",
"violated-directive": "script-src 'self' https://apis.google.com",
"original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"
}
}
Isso contém várias informações que vão ajudar a rastrear a
causa específica da violação, incluindo a página em que ela
ocorreu (document-uri
), o referenciador da página (ao contrário do campo de cabeçalho
HTTP, a chave não está incorreta), o recurso que violou a
política da página (blocked-uri
), a diretiva específica violada
(violated-directive
) e a política completa da página (original-policy
).
Somente relatório
Se você está começando a usar a CSP, faz sentido avaliar o estado atual do seu aplicativo antes de implementar uma política drástica para os usuários.
Como degrau para uma implantação completa, você pode pedir ao navegador para monitorar
uma política, relatando violações, mas não aplicando as restrições. Em vez de
enviar um cabeçalho Content-Security-Policy
, envie um
cabeçalho Content-Security-Policy-Report-Only
.
Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;
A política especificada no modo somente denúncias não bloqueia recursos restritos, mas envia denúncias de violação ao local especificado. É possível até enviar os dois cabeçalhos, aplicando uma política enquanto monitora a outra. Essa é uma ótima maneira de avaliar o efeito das alterações na CSP do seu aplicativo: ative a geração de relatórios para uma nova política, monitore os relatórios de violação e corrija os bugs que surgirem. Quando você estiver satisfeito com o efeito, comece a aplicar a nova política.
Uso no mundo real
A CSP 1 pode ser usada no Chrome, Safari e Firefox, mas tem suporte muito limitado no IE 10. Veja mais detalhes em caniuse.com (link em inglês). A CSP de nível 2 está disponível no Chrome desde a versão 40. Grandes sites, como o Twitter e o Facebook, implantaram o cabeçalho (vale a pena ler o estudo de caso do Twitter), e o padrão já está pronto para você começar a implantar nos seus próprios sites.
A primeira etapa para criar uma política para seu aplicativo é avaliar os recursos que estão sendo carregados. Quando você achar que entende como as coisas são organizadas no seu app, configure uma política com base nesses requisitos. Vamos analisar alguns casos de uso comuns e determinar como podemos oferecer suporte a eles dentro dos limites de proteção da CSP.
Caso de uso 1: widgets de mídias sociais
O Botão +1 do Google inclui um script de
https://apis.google.com
e incorpora um<iframe>
dehttps://plusone.google.com
. Você precisa de uma política que inclua as duas origens para incorporar o botão. Uma política mínima seriascript-src https://apis.google.com; child-src https://plusone.google.com
. Você também precisa garantir que o snippet de JavaScript fornecido pelo Google seja extraído em um arquivo JavaScript externo. Se você tinha uma política baseada em nível 1 usandoframe-src
, o nível 2 exigia a alteração dela parachild-src
. Isso não é mais necessário no CSP de nível 3.O botão "Curtir" do Facebook tem várias opções de implementação. Recomendamos manter a versão
<iframe>
, já que ela é colocada no sandbox com segurança do restante do site. Ele exige uma diretivachild-src https://facebook.com
para funcionar corretamente. Por padrão, o código<iframe>
fornecido pelo Facebook carrega um URL relativo,//facebook.com
. Altere-o para especificar HTTPS explicitamente:https://facebook.com
. Não há motivos para usar HTTP se não for necessário.O botão Tweet do Twitter depende do acesso a um script e frame, ambos hospedados em
https://platform.twitter.com
. O Twitter também fornece um URL relativo por padrão. Edite o código para especificar HTTPS ao copiá-lo e colá-lo localmente. Tudo estará pronto para usarscript-src https://platform.twitter.com; child-src https://platform.twitter.com
, desde que você mova o snippet JavaScript fornecido pelo Twitter para um arquivo JavaScript externo.Outras plataformas têm requisitos semelhantes e podem ser tratadas de forma semelhante. Sugerimos que você configure apenas um
default-src
de'none'
e monitore o console para determinar quais recursos precisam ser ativados para que os widgets funcionem.
Incluir vários widgets é simples: basta combinar as diretivas de política, lembrando de mesclar todos os recursos de um único tipo em uma única diretiva. Se você quisesse os três widgets de mídia social, a política seria assim:
script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com
Caso de uso 2: bloqueio
Vamos supor por um momento que você administra o site de um banco e quer ter certeza de que apenas os recursos que você escreveu possam ser carregados. Nesse cenário,
comece com uma política padrão que bloqueie absolutamente tudo (default-src 'none'
) e continue a partir daí.
Digamos que o banco carregue todas as imagens, estilo e script de uma CDN em
https://cdn.mybank.net
e se conecte via XHR a https://api.mybank.com/
para
extrair vários bits de dados. Os frames são usados, mas somente para páginas locais do
site (sem origens de terceiros). Não há Flash, fontes nem
extras no site. O cabeçalho CSP mais restritivo que poderíamos enviar é este:
Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src 'self'
Caso de uso 3: somente SSL
Um administrador de um fórum de discussão sobre alianças de casamento quer garantir que todos os recursos sejam carregados apenas por canais seguros, mas não escreve muito código. Reescrever grandes partes do software de fórum de terceiros, cheio de scripts e estilo embutidos, está além das habilidades dele. A política a seguir seria eficaz:
Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'
Mesmo que https:
seja especificado em default-src
, as diretivas de script e estilo não herdam essa origem automaticamente. Cada diretiva substitui completamente o padrão para esse tipo específico de recurso.
O futuro
O nível 2 da Política de Segurança de Conteúdo é uma recomendação candidata. O Grupo de trabalho de segurança de aplicativos da Web do W3C já começou a trabalhar na próxima iteração da especificação, a Política de segurança de conteúdo de nível 3.
Se você estiver interessado na discussão sobre esses futuros recursos, dê uma olhada nos arquivos da lista de e-mails public-webappsec@ ou participe você mesmo.