O modelo de segurança da Web se baseia na
política de mesma origem. O código
de https://mybank.com
só pode ter acesso aos dados de
https://mybank.com
, e https://evil.example.com
certamente nunca pode ter acesso.
Cada origem é mantida isolada do resto da Web, oferecendo aos desenvolvedores um sandbox
seguro para criar e testar. Na teoria, isso é simplesmente brilhante. Na
prática, os invasores encontraram maneiras inteligentes de burlar o sistema.
Os ataques de scripting em vários locais (XSS), por exemplo, fogem da mesma política de origem fazendo um site enviar código malicioso junto com o conteúdo pretendido. Esse é um grande problema, porque os navegadores confiam que todo o código mostrado em uma página é uma parte legítima da origem de segurança da página. A Folha de referência de XSS é um detalhamento antigo, mas representativo, dos métodos que podem ser usados por um invasor para violar essa confiança por meio da injeção de código malicioso. Se um invasor injetar qualquer código, é praticamente o fim: os dados da sessão do usuário serão comprometidos e as informações que precisam ser mantidas em segredo serão exfiltradas para os bandidos. É óbvio que gostaríamos de evitar isso, se possível.
Esta visão geral destaca uma defesa que pode reduzir consideravelmente o risco e o impacto dos ataques de 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 o cliente do que é permitido e do que não é.
- Conheça as diretivas disponíveis.
- Conheça as palavras-chave que usam.
- Código inline e
eval()
são considerados perigosos. - Relate violações da política ao servidor antes de tomar alguma providência sobre elas.
Listas de permissões de origem
O problema explorado por ataques de XSS é a incapacidade do navegador de distinguir
entre o script que faz parte do seu aplicativo e o script
injetado de forma maliciosa por um terceiro. Por exemplo, o botão +1 do Google na
parte de baixo desta página carrega e executa o código de
https://apis.google.com/js/plusone.js
no contexto da origem da 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 baixa e executa superalegre todo código que a página
solicita, sem considerar a fonte.
Em vez de confiar cegamente em tudo que um servidor entrega, 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 script, o script não vai passar da lista de permissões e, por isso, não vai ser
executado.
Já que confiamos em apis.google.com
para fornecer código válido e confiamos em nós mesmos
para fazer o mesmo, vamos definir uma política que só permite a execução de script quando ele
vem de uma dessas duas fontes:
Content-Security-Policy: script-src 'self' https://apis.google.com
Simples, certo? Como você já deve ter imaginado, 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. Obedientemente, o navegador baixa e executa JavaScript de
apis.google.com
por HTTPS e da origem da página atual.
Com essa política definida, o navegador simplesmente exibe um erro, em vez de carregar o script de outra fonte. Quando um invasor inteligente consegue injetar código no seu site, ele se depara precipitadamente com uma mensagem de erro, em vez de conseguir fazer o que pretende.
A política é aplicável a diversos recursos
Enquanto que os recursos de script representam os riscos de segurança mais óbvios, o CSP oferece um amplo
conjunto de diretivas de política que proporcionam controle bem avançado dos recursos
que uma página pode carregar. Como você já viu script-src
, o conceito
deve estar claro.
Vamos dar uma breve pincelada sobre 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 foi 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 para workers e conteúdos de frame incorporados. Por exemplo:child-src https://youtube.com
permitiria vídeos incorporados 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 oferecer 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. Essa 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 que não são HTML.frame-src
foi descontinuado no nível 2, mas restaurado no nível 3. Se não estiver presente, ele ainda vai voltar parachild-src
como antes.img-src
define as origens de onde as imagens podem ser carregadas.media-src
restringe as origens que têm permissão para fornecer vídeo e áudio.object-src
permite controlar o Flash e outros plug-ins.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, mudando HTTP para HTTPS. Essa diretiva é para sites com um grande número de URLs antigos que precisam ser regravados.worker-src
é uma diretiva do nível 3 do CSP 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 fonte válida. Por exemplo, você pode carregar fontes de
qualquer lugar, sem restrições.
É possível modificar 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. Em geral, isso se aplica a qualquer diretiva que
termine com -src
. Se default-src
estiver definido como https://example.com
e você não
especificar uma diretiva font-src
, só será possível carregar fontes de
https://example.com
. Especificamos apenas script-src
nos
exemplos anteriores, o que significa que é possível carregar imagens, fontes e outros
de qualquer origem.
As diretivas a seguir não usam default-src
como um fallback. Lembre-se de que
não defini-las é o mesmo que permitir tudo.
base-uri
form-action
frame-ancestors
plugin-types
report-uri
sandbox
Você pode usar quantas diretivas quiser, de acordo com o apropriado para o
aplicativo específico: basta listar cada uma no cabeçalho HTTP, separando
as diretivas por ponto e vírgula. Não deixe de listar todos
os recursos exigidos de um tipo específico em uma única diretiva. Se você gravou
algo como script-src https://host1.com; script-src https://host2.com
, a
segunda diretiva seria simplesmente ignorada. O exposto 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
não precisa de conteúdo em quadros nem plug-ins, sua política vai ficar mais ou menos
como esta:
Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'
Detalhes da implementação
Você vai encontrar os cabeçalhos X-WebKit-CSP
e X-Content-Security-Policy
em vários
tutoriais na Web. Indo além, você deve ignorar esses cabeçalhos
prefixados. Navegadores modernos (com exceção do IE) oferecem suporte ao cabeçalho
Content-Security-Policy
não prefixado. Esse é o cabeçalho que você deve usar.
Independentemente do cabeçalho que você usar, a política é definida para cada página: você precisa enviar o cabeçalho HTTP junto com todas as respostas que você quer garantir que estejam protegidas. Isso dá muita flexibilidade, já que você pode ajustar a política para páginas específicas de acordo com a necessidade. Talvez um conjunto de páginas do 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 de cada diretiva é flexível. Você pode especificar fontes por
esquema (data:
, https:
) ou de acordo com a especificidade, desde apenas por nome de 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 a HTTPS, apenas
example.com
e apenas a porta 443). Caracteres curinga são aceitos, mas somente como um esquema,
uma porta ou à extrema 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:
'none'
, como é de se esperar, não corresponde a nada.'self'
corresponde à origem atual, mas não aos subdomínios.'unsafe-inline'
permite JavaScript e CSS inline. (Vamos falar sobre isso em mais detalhes mais adiante).'unsafe-eval'
permite mecanismos de texto para JavaScript, comoeval
. (Esse também será um problema para nós).
Essas palavras-chave exigem aspas simples. Por exemplo, script-src 'self'
(com aspas)
autoriza a execução de JavaScript no host atual. script-src self
(sem aspas) permite executar JavaScript de um servidor chamado "self
" (e não do
host atual), o que provavelmente não é o que você quer.
Sandbox
Há mais uma diretiva de que vale a pena falar: sandbox
. Ela é um pouco
diferente das outras que analisamos, já que coloca restrições em ações que
a página pode executar, em vez de em recursos que a página 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 gerar um monte de
efeitos na página: forçar a página em uma origem única, impedir o envio
de formulários e outros. Está um pouco além do escopo deste artigo, mas você
pode encontrar todos os detalhes sobre os atributos de sandbox válidos na
seção "Sandboxing" da especificação HTML5.
A metatag
O mecanismo de fornecimento de CSPs preferido é um cabeçalho HTTP. No entanto, pode ser útil
definir uma política em uma página diretamente na marcação. Faça isso usando 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'"
/>
Não é possível usar esse recurso para frame-ancestors
, report-uri
ou sandbox
.
O código inline é considerado perigoso
Deve ficar claro que a CSP é baseada em origens de lista de permissões, já que essa é uma
maneira direta, sem ambiguidade, 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 apresentada pelos ataques XSS: a injeção de script inline.
Se um invasor conseguir injetar uma tag de script que contenha diretamente um payload
malicioso (<script>sendMyDataToEvilDotCom();</script>
),
o navegador não terá um mecanismo para diferenciá-la de uma tag de script inline
legítima. A CSP resolve esse problema banindo totalmente o script inline:
essa é a única forma de garantir.
Esse banimento inclui não apenas scripts incorporados diretamente nas tags script
, mas também
gerenciadores de eventos inline e URLs javascript:
. Você vai precisar mover o conteúdo das
tags script
para um arquivo externo e substituir os URLs javascript:
e <a ... onclick="[JAVASCRIPT]">
por chamadas addEventListener()
apropriadas. Por exemplo,
você pode reescrever o seguinte:
<script>
function doAmazingThings() {
alert('YOU AM AMAZING!');
}
</script>
<button onclick="doAmazingThings();">Am I amazing?</button>
para algo 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 regravado tem diversas vantagens além de funcionar bem com CSPs: é a melhor prática, use CSPs ou não. O JavaScript inline mistura estrutura e comportamento exatamente da forma que 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ê vai escrever código melhor se der ao 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 consolidadas em folhas de estilo externas para proteger contra uma
variedade de métodos de extração de dados surpreendentemente inteligentes
que o CSS permite.
Se você precisa ter script e estilo inline, ative
adicionando 'unsafe-inline'
como uma fonte permitida em uma diretiva script-src
ou
style-src
. Você ainda pode usar um nonce ou hash (veja abaixo), mas isso não é o mais indicado.
Banir script inline é a melhor medida de segurança da CSP, e
banir estilo inline aumenta a segurança do aplicativo. É preciso um pouco de
esforço no início para garantir que as coisas funcionem corretamente após mover todo o código
para fora da linha, mas essa é uma medida que vale muito a pena tomar.
Se for imprescindível usar
A CSP de nível 2 oferece retrocompatibilidade com scripts inline ao permitir que você adicione scripts inline 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 pesado, é útil como alternativa.
Para usar um valor de uso único, atribua um atributo de uso único à tag de script. O valor precisa ser igual 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 valor de uso único à diretiva script-src
anexada à palavra-chave nonce-
.
Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'
Lembre-se de que é necessário gerar um nonce para cada solicitação de página, e eles não podem ser adivinháveis.
Os hashes funcionam mais ou menos da mesma forma. Em vez de adicionar código à tag "script",
crie um hash SHA do próprio script e adicione-o à diretiva script-src
.
Por exemplo, digamos que sua página tenha o seguinte:
<script>
alert('Hello, world.');
</script>
Sua política conteria isto:
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 fazem diferença, incluindo espaços no início e
no fim.
Uma pesquisa no Google sobre geração de hashes SHA vai levar você a soluções em diversas linguagens. Usando o Chrome 40 ou posterior, você pode abrir o DevTools e recarregar a página. A guia "Console" vai conter mensagens de erro com o hash sha256 correto para cada um dos scripts inline.
Avaliação também
Mesmo que um invasor não consiga injetar o script diretamente, ele pode enganar
seu aplicativo para converter um texto inerte em JavaScript executável
e executá-lo em nome dele. eval()
, new
Function() , setTimeout([string], ...)
e
setInterval([string], ...)
são vetores por meio dos quais o texto injetado
pode acabar executando algo inesperado e malicioso. A resposta padrão
da CSP a esse risco é bloquear completamente todos esses vetores.
Isso gera alguns impactos na forma com que você desenvolve aplicativos:
- Você precisa analisar o JSON com o
JSON.parse
integrado em vez de depender deeval
. Há operações JSON nativas em todos os navegadores desde o IE8, e elas são totalmente seguras. - Reescreva todas as chamadas de
setTimeout
ousetInterval
que você está fazendo com funções inline em vez de strings. Exemplo:
setTimeout("document.querySelector('a').style.display = 'none';", 10);
seria melhor se fosse:
setTimeout(function () {
document.querySelector('a').style.display = 'none';
}, 10);
- Evite a geração de modelos inline em tempo de execução: muitas bibliotecas de modelo usam
new Function()
amplamente para acelerar a geração de modelos em tempo de execução. É uma aplicação inteligente de programação dinâmica, mas traz consigo o risco de avaliar texto malicioso. Alguns frameworks oferecem suporte a CSP de forma inovadora, usando fallback para 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, como a Handlebars,
por exemplo. Pré-compilar modelos pode deixar a experiência do usuário mais
rápida até do que a implementação em tempo de execução mais rápida, além de ser mais segura. Se eval e
seu semelhante de "texto para JavaScript" forem essenciais para o aplicativo, você pode
ativá-los adicionando 'unsafe-eval'
como uma fonte permitida em uma diretiva
script-src
, mas recomendamos não fazer isso. Excluir a capacidade de executar
strings dificulta muito a execução de código
não autorizado no site.
Relatórios
A capacidade da CSP de bloquear recursos não confiáveis no cliente é um grande benefício para os
usuários, mas seria muito útil enviar algum tipo de notificação
de volta ao servidor para que você possa identificar e aparar erros que possam permitir
injeção de código malicioso. 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 vão ficar mais ou menos assim:
{
"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 vão ajudar a rastrear a
causa específica da violação, incluindo a página em que ela
ocorreu (document-uri
), o referrer dessa página (diferente do campo de cabeçalho
HTTP, a chave não está com erro de ortografia), o recurso que violou a
política da página (blocked-uri
), a diretiva específica que ela violou
(violated-directive
) e a política completa da página (original-policy
).
Somente relatórios
Se você está começando a usar o CSP, é recomendável avaliar o estado
atual do seu aplicativo antes de implementar uma política draconiana para os usuários.
Como um passo para uma implantação completa, você pode pedir ao navegador para monitorar
uma política, informando 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 relatório não bloqueia recursos restritos, mas envia relatórios de violação ao local que você especificar. Você ainda pode 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 para uma nova política, monitore os relatórios de violação e corrija os bugs que aparecerem. Quando você considerar o efeito satisfatório, é só começar a impor a nova política.
Uso no mundo real
A CSP 1 é bem útil no Chrome, Safari e Firefox, mas tem suporte muito limitado no IE 10. É possível ver as especificidades em caniuse.com. A CSP de nível 2 está disponível no Chrome desde a versão 40. Sites de massa, como o Twitter e o Facebook, implementaram 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 implementar nos seus 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 se relacionam no seu app, defina uma política com base nesses requisitos. Vamos falar sobre alguns casos de uso comuns e determinar qual seria a melhor forma de oferecer suporte a eles dentro do campo de proteção da CSP.
Caso de uso 1: widgets de redes 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 contemple 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 no nível 1 usandoframe-src
, o nível 2 exigia que você a mudasse parachild-src
. Isso não é mais necessário no nível 3 do CSP.O botão de curtidas do Facebook tem várias opções de implementação. Recomendamos trabalhar com a versão
<iframe>
, já que é isolada com segurança do resto do site. Ele precisa de uma diretivachild-src https://facebook.com
para funcionar corretamente. Observe que, 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 tem motivo para usar HTTP se não for necessário.O botão de Tuíte do Twitter funciona com acesso a um script e um 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 copiar/colar localmente. Você vai estar pronto comscript-src https://platform.twitter.com; child-src https://platform.twitter.com
, desde que mova o snippet de JavaScript fornecido pelo Twitter para um arquivo JavaScript externo.Outras plataformas têm requisitos similares e podem ser endereçadas de forma parecida. Sugerimos definir um
default-src
como'none'
e observar o console para determinar quais recursos serão necessários para que os widgets funcionem.
Incluir vários widgets é simples: basta combinar as diretivas de política, lembrando de agrupar todos os recursos de um único tipo em uma única diretiva. Se você quiser os widgets das três redes sociais, a política terá este formato:
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
Imagine por um momento que você administra o site de um banco e quer garantir que
somente os recursos que você mesmo desenvolveu sejam carregados. Nesse cenário,
comece com uma política padrão que bloqueie absolutamente tudo (default-src 'none'
) e parta daí.
Digamos que o banco carregue todas as imagens, estilo e scripts de um 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 seria:
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
O administrador de um fórum de discussão sobre anéis de casamento quer garantir que todos os recursos sejam carregados somente por canais seguros, mas não escreve muito código. Reescrever grandes quantidades de software de fórum de terceiros, cheio até a última gota de scripts e estilo embutidos, está além de suas habilidades. A política a seguir seria aplicada:
Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'
Embora https:
seja especificado em default-src
, as diretivas de script e estilo
não herdam essa fonte automaticamente. Cada diretiva substitui
completamente o padrão para esse tipo específico de recurso.
O futuro
A Política de segurança de conteúdo de nível 2 é uma recomendação candidata. O Web Application Security Working Group da 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 tiver interesse na discussão sobre esses recursos futuros, confira os arquivos da lista de e-mails public-webappsec@ ou participe.