O modelo de segurança da Web tem como base a
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
não pode ter acesso.
Cada origem é mantida isolada do restante da Web, oferecendo aos desenvolvedores um ambiente
sandbox seguro para criar e brincar. Na teoria, isso é perfeitamente brilhante. Na
prática, os invasores encontraram maneiras inteligentes de subverter o sistema.
Os ataques de scripting em vários locais (XSS), por exemplo, ignoram a política de mesma origem enganando um site para entregar um código malicioso junto com o conteúdo pretendido. Isso é um problema enorme, porque os navegadores confiam em todo o código que aparece em uma página como parte legítima da origem de segurança dela. A XSS Cheat Sheet é uma seção transversal antiga, mas representativa, dos métodos que um invasor pode usar para violar essa confiança ao injetar código malicioso. Se um invasor injetar qualquer código, o jogo acabou: os dados da sessão do usuário serão comprometedores, e as informações que precisam ser mantidas em segredo serão exfiltradas para os bandidos. Obviamente, queremos evitar isso.
Esta visão geral destaca 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 e o que não é.
- Saiba quais diretivas estão disponíveis.
- Saiba quais são as palavras-chave que eles usam.
- O código inline e o
eval()
são considerados nocivos. - Denuncie violações da 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 o script que faz parte do aplicativo e o que foi
injetado maliciosamente por terceiros. 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
nele, 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 que uma página
solicita, independentemente da origem.
Em vez de confiar cegamente em tudo o 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 encontre uma falha para
injetar o script, ele não vai corresponder à lista de permissões e, portanto, não será
executado.
Como confiamos que o apis.google.com
vai entregar um código válido e que também confiamos
em nós mesmos, vamos definir uma política que só permite a execução do script quando ele
vem de uma dessas duas fontes:
Content-Security-Policy: script-src 'self' https://apis.google.com
Simples, certo? Como você provavelmente já deduziu, 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. O navegador faz o download e executa 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 fonte. Quando um invasor habilidoso consegue injetar código no seu site, ele se depara com uma mensagem de erro, em vez do sucesso que esperava.
A política se aplica a uma grande variedade de recursos
Embora os recursos de script sejam os riscos de segurança mais óbvios, o CSP oferece um conjunto
de diretrizes de política que permitem um controle bastante granular sobre os recursos
que uma página pode carregar. Você já viu script-src
, então o conceito
deve estar claro.
Vamos conferir rapidamente o restante das diretivas de recursos. 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 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 (por XHR, WebSockets e EventSource).font-src
especifica as origens que podem fornecer fontes da Web. As fontes da Web do Google podem ser ativadas usandofont-src https://themes.googleusercontent.com
.form-action
lista os 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 nã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.- O
media-src
restringe as origens permitidas para enviar vídeo e áudio. - O
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 para onde um navegador vai 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 reescrever esquemas de URL, mudando o HTTP para HTTPS. Essa diretiva é para sites com um grande número de URLs antigos que precisam ser reescritos.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 ficam 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 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. 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 conseguir
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 carregadas de
qualquer origem.
As diretivas a seguir não usam default-src
como substituto. Não definir essas configurações é o mesmo que permitir qualquer coisa.
base-uri
form-action
frame-ancestors
plugin-types
report-uri
sandbox
Você pode usar quantas diretivas quiser ou precisar para seu
aplicativo específico, simplesmente listando cada uma no cabeçalho HTTP, separando
as diretivas com ponto e vírgula. Liste todos
os recursos necessários de um tipo específico em uma única diretiva. Se você escrever
algo como script-src https://host1.com; script-src https://host2.com
, a
segunda diretiva será simplesmente ignorada. Algo como o exemplo abaixo
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 ou plug-ins enquadrados, 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ê vai encontrar cabeçalhos X-WebKit-CSP
e X-Content-Security-Policy
em vários
tutoriais na Web. Ignore esses cabeçalhos
com prefixo. Os navegadores modernos (com exceção do IE) oferecem suporte ao cabeçalho
Content-Security-Policy
sem prefixo. Esse é o cabeçalho que você precisa 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 proteger. Isso oferece muita flexibilidade, já que você pode ajustar a política para páginas específicas com base nas necessidades delas. Talvez um conjunto de páginas no seu site tenha um botão +1, enquanto outras não têm. Você pode permitir que o código do botão seja carregado apenas quando necessário.
A lista de origem em cada diretiva é flexível. É possível especificar as origens por
esquema (data:
, https:
) ou variar a especificidade de apenas o 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 a HTTPS, apenas
example.com
e apenas a porta 443). Os 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 o próprio example.com
), usando
qualquer esquema, em qualquer porta.
A lista de origem também aceita quatro palavras-chave:
'none'
, como você pode imaginar, 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 com mais detalhes em breve.'unsafe-eval'
permite mecanismos de conversão de texto em JavaScript, comoeval
. Vamos abordar isso também.
Essas palavras-chave exigem aspas simples. Por exemplo, script-src 'self'
(com aspas)
autoriza a execução de JavaScript do 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 que vale a pena discutir: sandbox
. Ela é um pouco
diferente das outras que analisamos, porque impõe restrições às ações que
a página pode realizar, em vez de 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 ter uma ampla gama de
efeitos na página: forçar a página a uma origem exclusiva e impedir o envio
de formulários, entre outros. Isso está um pouco fora do escopo deste artigo, mas você
pode encontrar todos os detalhes sobre atributos de sandbox válidos na
seção "Sandboxing" da especificação 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. 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 nocivo
O CSP é baseado em origens de lista de permissões, porque é 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 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. O CSP resolve esse problema banindo completamente o script inline:
é a única maneira de ter certeza.
Essa proibição inclui não apenas scripts incorporados diretamente em 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 URLs javascript:
e <a ... onclick="[JAVASCRIPT]">
por chamadas addEventListener()
adequadas. Por exemplo,
você pode reescrever o seguinte de:
<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 reescrito tem várias vantagens além de funcionar bem com CSP. Ele já é uma prática recomendada, independentemente do uso do CSP. O JavaScript inline mistura estrutura e comportamento exatamente da maneira 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 à compilação e minificação. Você vai escrever um código melhor se fizer o trabalho de mover o código para recursos externos.
O estilo inline é tratado da mesma maneira: o atributo style
e as tags
style
precisam ser consolidados em folhas de estilo externas para proteger contra uma
variedade de métodos de exfiltração de dados surpreendentemente inteligentes
que o CSS permite.
Se você precisar de script e estilo inline, 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), mas não é recomendável.
A proibição de scripts inline é a maior vitória de segurança que o CSP oferece, e
a proibição de estilos inline também protege seu aplicativo. É necessário um pouco
de esforço para garantir que as coisas funcionem corretamente depois de mover todo o código
fora da rede, mas essa é uma troca que vale a pena.
Se você realmente precisar usar
O CSP Nível 2 oferece compatibilidade com versões anteriores para scripts inline, permitindo que você adicione scripts inline específicos à lista de permissões usando um valor de uso único (número usado uma vez) ou um hash. Embora isso possa ser complicado, é útil em casos extremos.
Para usar um valor de uso único, atribua um atributo de uso de script à 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 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 regenerados para cada solicitação de página e não podem ser previsíveis.
Os hashes funcionam da mesma forma. Em vez de adicionar código à tag do script,
crie uma hash SHA do script e adicione à diretiva script-src
.
Por exemplo, digamos que sua página tenha o seguinte:
<script>
alert('Hello, world.');
</script>
Sua política teria 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. O CSP também
oferece suporte a sha384-
e sha512-
. Ao gerar o hash, não inclua as
tags <script>
. Além disso, o uso de letras maiúsculas e minúsculas e espaços em branco, incluindo espaços em branco à esquerda ou
à direita, são importantes.
Uma pesquisa no Google sobre como gerar hashes SHA vai levar você a soluções em vários idiomas. No Chrome 40 ou mais recente, 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 do CSP
a esse risco é bloquear completamente todos esses vetores.
Isso afeta a maneira como você cria aplicativos:
- É necessário 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 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 o uso de modelos inline no momento da execução: muitas bibliotecas de modelos usam
new Function()
com frequência para acelerar a geração de modelos no momento da execução. É uma aplicação inteligente de programação dinâmica, mas corre o risco de avaliar texto malicioso. Alguns frameworks oferecem suporte ao CSP pronto para uso, retornando 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 modelagem que ofereça
pré-compilação (o Handlebars, por exemplo). A pré-compilação dos modelos pode tornar a experiência do usuário ainda
mais rápida do que a implementação de execução mais rápida, além de ser mais segura. Se eval e
os irmãos de conversão de texto em JavaScript forem essenciais para seu app, você poderá
ativá-los adicionando 'unsafe-eval'
como uma origem permitida em uma diretiva
script-src
, mas não recomendamos isso. Proibir a capacidade de executar
strings dificulta muito a execução de código não autorizado
no seu site.
Relatórios
A capacidade do CSP de bloquear recursos não confiáveis do lado do cliente é uma grande vitória para seus
usuários, mas seria muito útil ter algum tipo de notificação
enviada de volta ao servidor para que você possa identificar e corrigir bugs que permitem
injeção maliciosa. Para isso, é possível 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 ter 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 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 rígida 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 para o local especificado. Você pode até enviar ambos os cabeçalhos, aplicando uma política enquanto monitora outra. Essa é uma ótima maneira de avaliar o efeito das mudanças no CSP do seu aplicativo: ative o envio de relatórios para uma nova política, monitore os relatórios de violação e corrija os bugs que aparecerem. Quando estiver satisfeito com o efeito, comece a aplicar a nova política.
Uso no mundo real
O CSP 1 é bastante utilizável no Chrome, Safari e Firefox, mas tem suporte muito limitado no IE 10. Confira detalhes em caniuse.com. O CSP Nível 2 está disponível no Chrome desde a versão 40. Sites grandes, como o Twitter e o Facebook, já implantaram o cabeçalho (leia o estudo de caso do Twitter), e o padrão está pronto para você começar a implantar nos seus sites.
A primeira etapa para criar uma política para seu aplicativo é avaliar os recursos que você está carregando. Quando você achar que já sabe como as coisas são organizadas no app, configure uma política com base nesses requisitos. Vamos analisar alguns casos de uso comuns e determinar a melhor forma de oferecer suporte a eles dentro dos limites de proteção do 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 essas 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 de 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 do Facebook tem várias opções de implementação. Recomendamos usar a versão
<iframe>
, porque ela está em um sandbox seguro em relação ao restante do site. Ele exige 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
. Mude isso para especificar explicitamente o HTTPS:https://facebook.com
. Não há motivo para usar HTTP se não for necessário.O botão Tweet do Twitter depende do acesso a um script e a 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 semelhantes e podem ser abordadas de maneira semelhante. Sugerimos definir uma
default-src
de'none'
e observar o console para determinar quais recursos você precisa ativar para que os widgets funcionem.
A inclusão de vários widgets é simples: basta combinar as diretivas de políticas, lembrando de mesclar todos os recursos de um único tipo em uma única diretiva. Se você quiser usar os três widgets de mídia social, a política vai ficar 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
Suponha que você administre um site de banco e queira garantir que
somente os recursos que você mesmo escreveu possam ser carregados. Nesse cenário,
comece com uma política padrão que bloqueia absolutamente tudo (default-src 'none'
) e desenvolva a partir daí.
Digamos que o banco carregue todas as imagens, estilos 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 apenas para páginas locais do
site (sem origens de terceiros). Não há Flash no site, nem fontes, nem
extras. O cabeçalho CSP mais restritivo que podemos 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 que está cheio de script e estilo inline está além das habilidades dele. 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 automaticamente essa origem. 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 nível 2 é 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, Política de segurança de conteúdo nível 3.
Se você tiver interesse na discussão sobre esses recursos futuros, confira os arquivos da lista de e-mails public-webappsec@ ou participe você mesmo.