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 https://evil.example.com
definitivamente não pode ter acesso permitido.
Cada origem é mantida isolada do restante da Web, oferecendo aos desenvolvedores um sandbox
seguro para criar e jogar. Em teoria, isso é perfeitamente brilhante. Na
prática, os invasores encontraram maneiras inteligentes de subverter o sistema.
Os ataques de scripting em vários sites (XSS), por exemplo, ignoram a mesma política de origem enganando um site para que entregue um código malicioso junto com o conteúdo pretendido. Esse é um grande problema, já que os navegadores confiam que todo o código exibido em uma página é parte legítima da origem de segurança dela. A Folha de referência do XSS é um detalhamento antigo, mas representativo, dos métodos que um invasor pode usar para violar essa confiança por meio da injeção de código malicioso. Se um invasor injetar qualquer código com sucesso, é praticamente o fim do jogo: os dados da sessão do usuário serão comprometidos e informações que deveriam ser mantidas em segredo são exfiltradas para os vilões. Obviamente, gostaríamos de evitar isso, se possível.
Nesta visão geral, destacamos uma defesa que pode reduzir significativamente o risco e o impacto dos 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.
- Descubra quais palavras-chave elas usam.
- O código inline e o
eval()
são considerados nocivos. - Denuncie violações da política ao 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 aplicativo e um script
injetado de forma maliciosa por um terceiro. Por exemplo, o botão +1 do Google no final 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 qualquer código solicitado pela 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
apenas recursos dessas fontes. Mesmo que um invasor encontre uma brecha para
injetar o script, o script não vai corresponder à lista de permissões e, portanto, não será
executado.
Como confiamos em apis.google.com
para fornecer um código válido e em nós mesmos
para fazer o mesmo, vamos definir uma política que só permita a execução do script quando
vier de uma dessas duas fontes:
Content-Security-Policy: script-src 'self' https://apis.google.com
Simples, certo? Como você provavelmente imaginou, script-src
é uma diretiva que controla um conjunto de privilégios relacionados a script de uma página específica. Especificamos
'self'
como uma fonte válida de script e https://apis.google.com
como
outra. O esperado é que o navegador faça o download e execute o JavaScript de
apis.google.com
por HTTPS, bem como da origem da página atual.
Com essa política definida, o navegador simplesmente gera um erro, em vez de carregar o script de qualquer outra origem. Quando um invasor inteligente injeta código no site, ele se depara com uma mensagem de erro em vez do sucesso esperado.
A política se aplica a vários 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íticas que permitem um controle bastante granular sobre os recursos que uma página pode carregar. Você já viu o script-src
, então o conceito
precisa ficar claro.
Vamos examinar rapidamente as outras 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 não é implementada em grande parte 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 o 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 a que você pode se conectar (via XHR, WebSockets e EventSource).font-src
especifica as origens que podem exibir fontes da Web. As fontes da Web do Google podem ser ativadas usandofont-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. Esta diretiva se aplica às tags<frame>
,<iframe>
,<embed>
e<applet>
. Essa diretiva não pode ser usada em tags<meta>
e se aplica apenas a recursos não HTML.- O
frame-src
foi descontinuado no nível 2, mas foi restaurado no nível 3. Caso contrário, ele ainda voltará parachild-src
, como antes. img-src
define as origens de onde as imagens podem ser carregadas.media-src
restringe as origens que podem enviar vídeo e áudio.object-src
permite controlar o Flash e outros plug-ins.- O
plugin-types
limita os tipos de plug-ins que uma página pode invocar. report-uri
especifica um URL pelo 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 contraparte descript-src
para folhas de estilo.upgrade-insecure-requests
instrui os user agents a regravar esquemas de URL, alterando HTTP para HTTPS. Essa diretiva é para sites com um grande número de URLs antigos que precisam ser reescritos.worker-src
é uma diretiva da CSP de nível 3 que restringe os URLs que podem ser carregados como worker, worker compartilhado ou service worker. Desde julho de 2017, essa diretiva tem implementações limitadas.
Por padrão, as diretivas são abertas. Se você não definir uma política específica para uma
diretiva, digamos, font-src
, essa diretiva vai se comportar por padrão como
se você tivesse especificado *
como a origem 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 não forem especificadas. Geralmente, isso se aplica a qualquer diretiva que
termina com -src
. Se default-src
for definido como https://example.com
e você não
especificar uma diretiva font-src
, poderá 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 substituta. Lembre-se de que
não defini-las é o mesmo que permitir tudo.
base-uri
form-action
frame-ancestors
plugin-types
report-uri
sandbox
É possível usar a quantidade de diretivas que fizerem sentido para seu aplicativo. Basta listar cada uma no cabeçalho HTTP, separando-as por ponto e vírgula. Liste todos
os recursos necessários de um tipo específico em uma única diretiva. Se você tivesse gravado
algo como script-src https://host1.com; script-src https://host2.com
, a
segunda diretiva seria simplesmente ignorada. O exemplo abaixo especificaria corretamente ambas as 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
não precisa de 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. A partir de agora, ignore esses cabeçalhos
prefixados. Os navegadores modernos (exceto o IE) oferecem suporte ao cabeçalho
Content-Security-Policy
não prefixado. Esse é o cabeçalho que você deve usar.
Independentemente do cabeçalho usado, a política é definida página por página: é necessário enviar o cabeçalho HTTP com todas as respostas que você quer garantir que estejam protegidas. Isso oferece muita flexibilidade, já que é possível ajustar a política para páginas específicas com base nas necessidades delas. Talvez um conjunto de páginas em seu site tenha um botão +1, enquanto outros não. Você pode permitir que o código do botão seja carregado somente quando necessário.
A lista de origens de cada diretiva é flexível. É possível especificar origens por
esquema (data:
, https:
) ou variando em especificidade de somente nome do host
(example.com
, que corresponde a qualquer origem nesse host: qualquer esquema, qualquer porta) para
um URI totalmente qualificado (https://example.com:443
, que corresponde apenas a HTTPS, somente
example.com
e somente porta 443). Caracteres curinga são aceitos, mas apenas como 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 o próprio example.com
), usando
qualquer esquema, em qualquer porta.
A lista de origens também aceita quatro palavras-chave:
'none'
, como é esperado, não corresponde a nada.'self'
corresponde à origem atual, mas não aos subdomínios dela.- O
'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
. Também vamos falar sobre isso.
Essas palavras-chave exigem aspas simples. Por exemplo, script-src 'self'
(entre aspas)
autoriza a execução de JavaScript do host atual. script-src self
(sem aspas) permite o 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 que vale a pena falar: sandbox
. É um pouco
diferente das outras que analisamos, já que coloca restrições em ações que
a página pode realizar, em vez de recursos que a página pode carregar. Se a
diretiva sandbox
estiver presente, a página vai ser tratada como se tivesse sido carregada
dentro de uma <iframe>
com um atributo sandbox
. Isso pode ter vários efeitos na página, como forçar a página a ter uma origem exclusiva e impedir o envio de formulários, entre outros. Está um pouco além do escopo deste artigo, mas você pode encontrar detalhes completos sobre os atributos de sandbox válidos na seção "Sandboxing" das especificações HTML5.
A metatag
O mecanismo de entrega preferido das 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'"
/>
Isso não pode ser usado para frame-ancestors
, report-uri
ou sandbox
.
O código embutido é considerado nocivo
Deve ficar claro que a CSP é baseada em origens da lista de permissões, já que essa é uma
forma 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 causada por ataques XSS: a injeção de script in-line.
Se um invasor puder injetar uma tag de script que contenha diretamente algum payload
mal-intencionado (<script>sendMyDataToEvilDotCom();</script>
),
o navegador não terá um mecanismo para distingui-lo de uma tag de script in-line
legítima. A CSP resolve esse problema banindo totalmente o script inline:
é a única maneira de ter certeza.
Esse banimento 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 os URLs javascript:
e <a ... onclick="[JAVASCRIPT]">
pelas chamadas addEventListener()
apropriadas. Por exemplo,
é possível reescrever o seguinte usando:
<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 várias vantagens além de funcionar bem com CSP. Essa já é uma prática recomendada, independentemente do uso da CSP. O JavaScript inline mistura estrutura e comportamento exatamente da maneira que você não deveria. Recursos externos são mais fáceis de armazenar em cache para os navegadores, mais compreensíveis para os desenvolvedores e favorecem a compilação e a minificação. Você escreverá um código melhor se fizer o trabalho de mover o código para recursos externos.
O estilo inline é tratado da mesma forma: o atributo style
e as tags style
precisam ser consolidados em folhas de estilo externas para se protegerem contra uma
variedade de métodos de exfiltração de dados
surpreendentemente inteligentes
que o CSS permite.
Se você precisa ter script e estilo inline, ative-os
adicionando 'unsafe-inline'
como uma fonte permitida em uma diretiva
script-src
ou style-src
. Também é possível usar um valor de uso único ou um hash (veja abaixo), mas isso não é recomendado.
Banir script inline é a maior vantagem de segurança da CSP.
Da mesma forma, banir estilo inline aumenta a proteção do seu aplicativo. É necessário um pouco
de esforço no início para garantir que as coisas funcionem corretamente depois de mover todo o código
para fora da linha, mas essa é uma troca que vale a pena fazer.
Se você realmente precisar 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 resultado.
Para usar um valor de uso único, atribua um atributo de valor de uso único à tag de script. O valor precisa corresponder a um da 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 nonce à diretiva script-src
anexada à palavra-chave nonce-
.
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
Lembre-se de que o valor de uso único precisa ser gerado novamente para cada solicitação de página e não pode ser adivinhado.
Os hashes funcionam mais ou menos da mesma forma. Em vez de adicionar o código à tag de script, crie um hash SHA do próprio script e adicione-o à diretiva script-src
.
Por exemplo, digamos que sua página continha o seguinte:
<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, sha256-
é usado. 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 fazem diferença, incluindo
espaços no início e no fim.
Uma pesquisa no Google sobre geração de hashes SHA levará você a soluções em várias linguagens. Usando o Chrome 40 ou posterior, você pode abrir o DevTools e recarregar a página. Essa guia contém mensagens de erro com o hash sha256 correto para cada um dos scripts inline.
Eval também
Mesmo quando um invasor não pode injetar script diretamente, ele pode enganar
seu aplicativo para que ele converta um texto inerte em JavaScript executável
e o execute em nome dele. eval()
, nova
Function(), setTimeout([string], ...)
e
setInterval([string], ...)
são todos vetores por meio dos quais o texto injetado
pode acabar executando algo inesperadamente malicioso. A resposta padrão da CSP a esse risco é bloquear completamente todos esses vetores.
Isso gera alguns impactos na maneira como você cria aplicativos:
- Analise 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 com funções in-line em vez de strings. Exemplo:
setTimeout("document.querySelector('a').style.display = 'none';", 10);
seria melhor escrito da seguinte forma:
setTimeout(function () {
document.querySelector('a').style.display = 'none';
}, 10);
- Evite modelos inline durante a execução: muitas bibliotecas de modelos usam o
new Function()
generativamente para acelerar a geração de modelos no momento da execução. É uma aplicação eficiente de programação dinâmica, mas apresenta o risco de avaliar textos maliciosos. Alguns frameworks oferecem suporte à CSP, voltando 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 (o Handlebars oferece,
por exemplo). Pré-compilar modelos pode tornar a experiência do usuário ainda
mais rápida do que a implementação mais rápida de tempo de execução, além de ser mais segura. Se eval e
a versão de texto para JavaScript forem essenciais para o aplicativo, ative-os
adicionando 'unsafe-eval'
como uma fonte permitida em uma diretiva script-src
, mas não é recomendável fazer isso. Banir a capacidade de executar
strings torna muito mais difícil para um invasor executar códigos
não autorizados no seu site.
Relatórios
A capacidade da CSP de bloquear recursos não confiáveis do lado do cliente é uma grande vantagem para os
usuários, mas seria muito útil enviar algum tipo de notificação
de volta ao servidor para identificar e eliminar bugs que permitam
injeção maliciosa. Para isso, é possível instruir o navegador a POST
relatórios de violação no formato 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 terão esta aparência:
{
"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"
}
}
Ele contém muitas informações que ajudam a rastrear a
causa específica da violação, incluindo a página em que a violação
ocorreu (document-uri
), o referenciador dessa página (ao contrário do campo de cabeçalho
HTTP, a chave não está escrita incorretamente), o recurso que violou a
política da página (blocked-uri
), a diretiva específica
(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 rígida para os usuários.
Como um degrau para uma implantação completa, você pode pedir ao navegador para monitorar uma política, relatando violações, mas sem aplicar 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úncia não bloqueia recursos restritos, mas envia relatórios de violação para o local especificado. É possível até enviar ambos os cabeçalhos, aplicando uma política e monitorando outra ao mesmo tempo. Essa é uma ótima maneira de avaliar o efeito das mudanças na CSP do seu aplicativo: ative os relatórios de uma nova política, monitore os relatórios de violação e corrija os bugs que surgirem. Quando o efeito for satisfatório, comece a aplicar a nova política.
Uso no mundo real
A CSP 1 é bastante utilizável no Chrome, Safari e Firefox, mas tem suporte muito limitado no IE 10. Veja mais detalhes em caniuse.com. A CSP de nível 2 está disponível no Chrome desde a versão 40. Sites grandes, como o Twitter e o Facebook, implantaram o cabeçalho. Vale a pena ler o estudo de caso do Twitter, e o padrão está pronto para ser implantado nos seus próprios sites.
A primeira etapa para criar uma política para o aplicativo é avaliar os recursos que você está carregando. 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 do campo 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
. Também é necessário garantir que o snippet de JavaScript fornecido pelo Google seja extraído em um arquivo JavaScript externo. Se você tinha uma política baseada no nível 1 que usaframe-src
O nível 2 exigiu que ela fosse alterada parachild-src
. Isso não é mais necessário na 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. Ela requer 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 explicitamente o HTTPS:https://facebook.com
. Não há motivo para usar HTTP se isso não for necessário.O botão Tweet do Twitter depende do acesso a um script e um frame, ambos hospedados em
https://platform.twitter.com
. Da mesma forma, o Twitter fornece um URL relativo por padrão. Edite o código para especificar HTTPS ao copiar e colar localmente. Tudo estará pronto comscript-src https://platform.twitter.com; child-src https://platform.twitter.com
, desde que você mova o snippet de JavaScript que o Twitter fornece para um arquivo JavaScript externo.Outras plataformas têm requisitos semelhantes e podem ser tratadas de forma semelhante. Sugerimos apenas definir um
default-src
de'none'
e observar o console para determinar quais recursos são necessários para que os widgets funcionem.
A inclusão de 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ídias sociais, a política ficaria 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 total
Suponha que você gerencie o site de um banco e queira ter certeza de que
apenas os recursos que você mesmo escreveu podem 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 origem de terceiros). Não há Flash, fontes nem
extras no site. O cabeçalho CSP mais restritivo que poderíamos enviar é:
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 no 3: somente SSL
Um administrador de um fórum de discussão de anéis de casamento quer garantir que todos os recursos sejam carregados apenas por canais seguros, mas não escreve muito código. Reescrever grandes blocos de software de fórum de terceiros cheios até a borda com script e estilo inline está além das habilidades dele. A seguinte política entrará em vigor:
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 (link em inglês).
Se tiver interesse na discussão sobre esses futuros recursos, dê uma olhada nos arquivos da lista de e-mails public-webappsec@ ou participe.