O que é HTTP Content-Security-Policy e como utilizar no PHP

Content-Security-Policy (CSP, para os íntimos) é um cabeçalho HTTP que podemos usar para fornecer ao navegador uma lista branca de recursos que nossa aplicação usará.
Entenda-se como recurso imagens, Javascript, CSS, fontes, frames, conexões websocket, actions de formulário, etc.

Nesse texto falo um pouco sobre esse cabeçalho (que tem várias particularidades) e mostro como implementar no PHP de forma fácil.

Direto ao ponto, a sintaxe básica do header é a seguinte:

Content-Security-Policy: <diretiva 1> <recurso 1> [recurso 2] [recurso 3] [recurso N];<diretiva 2> <recurso 1> [recurso 2] [recurso 3] [recurso N];...

Também é possível declarar essas regras numa meta tag do HTML, mas essa não é a minha forma preferida .

Em tempo de desenvolvimento você pode usar o cabeçalho Content-Security-Policy-Report-Only que tem a mesma sintaxe.
Ele não bloqueia nada, é útil para debugar e não subir nada bloqueado para produção.

Diretivas

As diretivas são o que definem o tipo de recurso a que a regra está se referindo. Seja script, imagem, etc.

A diretiva default-src é usada pelo browser se certas diretivas não estiverem presentes.
É o caso da script-src (Javascript), style-src (CSS), img-src (imagem), font-src (fontes), etc.

Abaixo vou listar algumas diretivas, as que julgo serem mais comuns para a maioria das aplicações.
Caso queira saber mais, no final do texto coloquei alguns links de referência.

  • connect-src — Chamadas AJAX, WebSocket, fetch(). Chamadas que não obedecerem as regras dessa diretiva fazem com que o browser emule uma resposta HTTP 400 (bad request).
  • default-src — Regra padrão para alguns tipos de diretivas, se não forem especificadas
  • font-src — Lista de lugares permitidos para carregar fontes por @font-face
  • form-action — Especifica para onde os formulários do seu site podem enviar informação
  • frame-src — Lista de frames permitidos
  • img-src — Lista de recursos de imagens válidos
  • media-src — O que pode ser carregado por <audio> ou <video>
  • script-src — Lista de recursos Javascript válidos
  • style-src — Lista de recursos CSS válidos

Recursos

Após a diretiva, incluímos uma lista de recursos. Eles podem ser especificados de algumas formas diferentes:

  • * — um coringa; habilita tudo, exceto URLs do tipo data:, blob:, filesystem:
  • 'none' — nenhum; img-src none impede qualquer imagem de ser carregada
  • 'self' — Permite carregar recursos da mesma origem (host e porta)
  • 'unsafe-inline' — Habilita carregamento de scripts ou CSS incorporados ao documento HTML, seja em atributos “style”, “onclick”, e URIs “javascript:”
  • data: — Habilita carregamento de recursos incorporadas via “data:” tipo imagens encodadas em base64
  • http://dominio.com ou https://dominio.com — Habilita carregamento de recursos de “dominio.com”, especificando o protocolo (HTTP ou HTTPS)
  • dominio.com — Habilita carregamento de recursos do domínio “dominio.com” independente do protocolo
  • *.dominio.com — Habilita carregamento de recursos de todos os subdomínios de “dominio.com” independente do protocolo
  • http: ou https: — Carrega recursos somente via HTTP ou HTTPS

OBS: Alguns tipos de recursos eu deixei de listar por serem mais complexos de explicar. A explicação sobre eles pode vir num próximo texto.

Reportando violações para um serviço externo

Uma diretiva interessante é a report-uri; nela você pode especificar uma
URL que o browser irá chamar toda vez que houver algum bloqueio.
O browser irá enviar um JSON via POST para essa URL com algumas informações
como o nome da regra bloqueada, qual o recurso e etc.

Você pode criar seu próprio endpoint para receber essa informação, mas existe
um serviço que você pode usar. É o Report URI; ele é pago, mas oferece um plano grátis que dá pro gasto.

Exemplos

Abaixo alguns exemplos só para ilustrar:

Content-Security-Policy: default-src 'self'
Permite carregamento de imagens, scripts, estilos CSS somente da mesma origem

Content-Security-Policy: default-src 'self';img-src 'self' data:;script-src 'self' https://code.jquery.com
Estilos CSS devem ser carregados somente da mesma origem, imagens podem ser carregadas da mesma origem ou de strings tipo “data:” e Javascripts podem ser carregados de “https://code.jquery.com” e da mesma origem

Nota: Essa parte do texto será atualizada conforme eu for tendo ideias ou vendo exemplos legais.

Um exemplo prático de CSP impedindo XSS

A forma mais básica de XSS é quando o usuário preenche um campo de formulário (comentário, por exemplo) e ao ser exibido o conteúdo o browser interpreta esse código como se viesse do próprio site.
Obviamente a filtragem do conteúdo deve também ser feita do lado da aplicação, escapando ou eliminando códigos. Mas é sempre bom ter uma camada a mais de segurança.

Sem CSP

O exemplo abaixo é um formulário HTML bem tosco, que tem um campo de texto onde o usuário pode digitar qualquer coisa.
Ao ser submetido, o script irá mostrar o que foi digitado nesse campo sem filtragem alguma (só exemplo, filtre sempre).

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>Teste CSP</title>
    </head>
    <body>
        <form method="post" action="">
            <label for="comment">Comentario</label>
            <textarea name="comment" rows="5" cols="200"><script>alert('codigo executado');</script></textarea>
            <input type="submit" value="Enviar">
        </form>
        <p><?=$_POST['comment']??'';?></p>
    </body>
</html>

Assim que você salvar e executar o arquivo acima, clicando no botão enviar você verá que um alert Javascript será exibido.
Mas esse alert só foi exibido porque está no campo de texto.
Poderia não ser um alerta, mas um código para travar seu navegador, por exemplo.

Com CSP

Para proteger esse formulário, basta enviar um simples cabeçalho “Content-Security-Policy” excluindo scripts inline da interpretação.
Scripts inline são aqueles que escrevemos diretamente no corpo do HTML. Ou seja, nossa regra dirá ao navegador para aceitar apenas scripts que vierem de um arquivo separado, somente do domínio atual.

Logo no início do arquivo anterior, adicione essas linhas PHP, antes de qualquer coisa:

<?php
header("Content-Security-Policy: script-src 'self'");
?>

Ao salvar e executar o arquivo acima, antes de clicar em enviar abra o console do seu browser.
Quando clicar em enviar, além do alert não ser exibido, o navegador irá reportar que um script não foi executado pois viola as regras de carregamento de script.
Para voltar a ser vulnerável você pode adicionar 'unsafe-inline' na regra. Mas deixe desse jeito mesmo que tá legal. 🙂

Implementação no PHP

Se você estiver desenvolvendo algo em PHP puro ou se seu framework não tiver algum módulo para CSP, abaixo vai um script simples para dar um start:

<?php
// Regras em formato de array para ficar fácil de manter
$rules = [
    'script-src' => [
        "'self'",
        'https://google.com',
    ],
    'style-src' => [
        "'self'",
    ],
];
$header = 'Content-Security-Policy: ';
foreach ($rules as $directive => $values) {
    $header .= sprintf('%s %s;', $directive, implode(' ', $values));
}
$header = trim($header, ';');
header($header);

Esse script deve ser incluído antes de ser impresso algum conteúdo na página, caso contrário teremos o erro “cabeçalhos já foram enviados”.

Módulos de alguns frameworks

Fim

Bom, é isso.
Não falei tudo o que eu gostaria de falar sobre CSP nesse texto, mas possivelmente falarei em algum próximo.
Espero que tenha curtido e qualquer crítica, elogio, xingamento ou sugestão…
Comment here or ping me on Twitter! 🙂

Referências

Esta seção será atualizada conforme eu for achando links interessantes:

2 respostas para “O que é HTTP Content-Security-Policy e como utilizar no PHP”

    1. Oi Onofre.
      O problema é que você colocou apenas 'self' na sua diretiva default-src.
      A página está tentando carregar o recurso do domínio cdn.linearicons.com.
      Você pode deixar sua instrução CSP assim:
      Content-Security-Policy: default-src 'self' https://cdn.linearicons.com
      E ir adicionando items nessa lista.

      Espero ter ajudado.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *