FNSP Web Page

Content-Security-Policy: o cabeçalho que trava o XSS que escapou

Escapar à saída é a primeira linha de defesa contra o XSS, e é a que resolve o problema. O Content-Security-Policy é a segunda: serve para o dia em que uma coisa escapou, e o script injetado já está na página a tentar correr. Não impede o furo — impede que o furo sirva para alguma coisa.

É também o único cabeçalho de segurança que consegue partir um site inteiro, e por isso vale a pena ver o caso difícil em vez do exemplo de manual.

1. O que este site já tem

{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'SAMEORIGIN' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{
  key: 'Permissions-Policy',
  value: 'camera=(), microphone=(), geolocation=(), payment=(), usb=()',
}

Quatro cabeçalhos, nenhum deles capaz de estragar nada: ou o site já cumpria o que eles dizem, ou não usava aquilo. O Permissions-Policy está lá precisamente para fechar a porta a um script de terceiros — hoje o do AdSense, amanhã outro — que peça câmara ou localização em nome do site.

O CSP não está na lista. E a razão não é esquecimento.

2. O inventário, que é o trabalho todo

Antes de escrever uma política há que saber o que a página carrega. Aqui:

  • Estilos em linha no conteúdo. O atributo style aparece em mais de mil elementos dos artigos herdados de 2009 — parágrafos, imagens, spans, itens de lista. Nenhuma política os aceita sem style-src 'unsafe-inline'.

  • Um script em linha que tem de correr primeiro. O do tema, que põe a classe no <html> antes de a página ser pintada. Tirá-lo traz de volta o clarão branco a cada navegação.

  • AdSense, que carrega scripts a partir de si próprio — autorizar o primeiro domínio não basta.

  • Analytics, de outro domínio ainda.

  • Iframes do YouTube, nos artigos de vídeo, que obrigam a um frame-src.

  • Imagens de dezenas de domínios de fora, o que deixa o img-src praticamente aberto.

Isto é o cenário difícil inteiro: conteúdo antigo que não se pode reescrever, e três terceiros que se querem manter. Uma política escrita de cabeça parte isto todo na primeira página.

3. Por isso se começa em modo de relatório

{ key: 'Content-Security-Policy-Report-Only', value: politica }

Com -Report-Only o browser avalia a política, comunica cada violação, e não bloqueia nada. Põe-se a correr sobre tráfego real durante uma semana — não sobre o que se imagina que o site carrega — e lê-se o que aparece. É a única forma honesta de descobrir o que falta, porque nenhum inventário feito à mão fica completo.

E com a armadilha dita à partida: o modo de relatório nunca parte nada, portanto nunca cria pressa. É muito comum ficar ligado dois anos a comunicar violações para um sítio que ninguém lê. O modo de relatório é um passo, não um estado.

4. O nonce, e porque é que aqui dá

Para o script do tema há duas saídas sem abrir a porta a todos: o hash do seu conteúdo, ou um nonce — um número usado uma vez, diferente em cada resposta, posto no cabeçalho e no <script>.

O nonce tem um requisito que se percebe mal: tem de ser diferente a cada resposta. Num HTML guardado em cache e servido mil vezes, o mesmo nonce vai com ele mil vezes, e deixa de valer alguma coisa. Aqui as páginas são renderizadas a cada pedido, portanto dá; num site estático não daria, e o hash passaria a ser a única resposta certa.

5. Duas coisas que não vale a pena fazer

A primeira é resolver o problema com script-src 'unsafe-inline'. Isso cancela o cabeçalho: a política passa a autorizar exactamente aquilo de que se estava à espera — script injetado a correr dentro da página. Um hash para cada script conhecido dá mais trabalho e é a diferença entre ter CSP e ter a aparência de CSP.

A segunda é apagar o X-Frame-Options depois de pôr frame-ancestors. O moderno substitui o antigo nos browsers que o entendem, e não há vantagem nenhuma em retirar o antigo: ficam os dois, que é um caso raro de redundância que não custa nada.

6. A ordem por que isto se faz

Aqui ainda não está feito, e a ordem é esta: montar a política em modo de relatório; deixá-la correr uma semana sobre tráfego real; dar nonce ao script do tema; aceitar o 'unsafe-inline' nos estilos como dívida do conteúdo antigo, documentada e não escondida; e só então trocar o cabeçalho pelo que bloqueia.

É deliberadamente lento. Um CSP ligado de uma vez num site com três terceiros e mil atributos style descobre-se ao domingo, quando alguém diz que as imagens desapareceram.

O resumo

O CSP não substitui escapar à saída, que continua a ser onde o XSS se resolve. O que ele faz é reduzir o estrago do que passou — e é um cabeçalho que se mede em inventário, não em configuração. Escrever a política leva dez minutos; saber o que a página carrega leva uma semana, e é esse o trabalho.

Comentários

Ainda ninguém comentou este artigo.

Voltar ao blog

Gostávamos de saber quantas pessoas visitam o site, com o Google Analytics. Sem a sua autorização não corre nada. Política de Privacidade.