O autor selecionou Girls Who Code para receber uma doação como parte do programa Write for DOnations.
Introdução
Ao visitar um site, diversos recursos são usados para carregá-lo e renderizá-lo. Como exemplo, quando você acessa https://www.digitalocean.com
, seu navegador baixa o HTML e CSS diretamente de digitalocean.com
. No entanto, imagens e outros ativos são baixados de assets.digitalocean.com
, e scripts de análise são carregados de seus respectivos domínios.
Alguns sites utilizam uma infinidade de serviços, estilos e scripts diferentes para carregar e renderizar seu conteúdo, e seu navegador executará tudo isso. Um navegador não sabe se o código é malicioso, então é responsabilidade do desenvolvedor proteger os usuários. Como pode haver muitos recursos em um site, ter um recurso no navegador que permita apenas recursos aprovados é uma boa maneira de garantir que os usuários não sejam comprometidos. É para isso que servem as Políticas de Segurança de Conteúdo (CSPs, na sigla em inglês).
Usando um cabeçalho CSP, um desenvolvedor pode permitir explicitamente que determinados recursos sejam executados enquanto impede todos os outros. Como a maioria dos sites pode ter mais de 100 recursos, e cada um deve ser aprovado para a categoria específica de recurso que é, implementar um CSP pode ser uma tarefa tediosa. No entanto, um site com um CSP será mais seguro, pois garante que apenas recursos aprovados sejam permitidos para serem executados.
Neste tutorial, você irá implementar um CSP em uma aplicação básica do Django. Você irá personalizar o CSP para permitir que certos domínios e recursos inline sejam executados. Opcionalmente, você também pode usar o Sentry para registrar violações.
Pré-requisitos
Para concluir este tutorial, você precisará de:
- A working Django project (version 3 or greater is preferred), either on your local machine or a DigitalOcean Droplet. If you don’t have one, you can create one with the tutorial, How to Install Django and Set Up a Development Environment on Ubuntu 20.04.
- A web browser like Firefox or Chrome and an understanding of browser network tools. For more on using browser network tools, check out the product documentation for the Network Monitor in Firefox or the DevTools Network Tab in Chrome. For more general guidance on browser developer tools, see the guide: What are Browser Developer Tools?
- Conhecimento de Python 3 e Django, que você pode adquirir através da série de tutoriais, Como Programar em Python e Desenvolvimento Django.
- Uma conta no Sentry para rastrear violações do CSP (opcional).
Passo 1 — Criando uma Visualização de Demonstração
Neste passo, você irá modificar como sua aplicação lida com visualizações para que você possa adicionar suporte ao CSP.
Como pré-requisito, você instalou o Django e configurou um projeto de exemplo. A visualização padrão no Django é muito simples para demonstrar todas as capacidades do middleware CSP, então você irá criar uma página HTML simples para este tutorial.
Navegue até a pasta do projeto que você criou nos pré-requisitos:
Enquanto estiver dentro do diretório django-apps
, crie seu ambiente virtual. Chamaremos de env
genérico, mas você deve usar um nome que faça sentido para você e seu projeto.
Agora, ative o ambiente virtual com o seguinte comando:
Dentro do ambiente virtual, crie um arquivo views.py
na pasta do seu projeto usando o nano
, ou seu editor de texto favorito:
Agora, você vai adicionar uma visão básica que irá renderizar um modelo index.html
que você criará em seguida. Adicione o seguinte ao views.py
:
Salve e feche o arquivo quando terminar.
Crie um modelo index.html
em um novo diretório templates
:
Adicione o seguinte ao index.html
:
A visão que criamos irá renderizar esta página HTML simples. Ela exibirá o texto Olá, Sammy! junto com uma imagem de Sammy, o Tubarão.
Salve e feche o arquivo quando terminar.
Para acessar esta visão, você precisará atualizar o urls.py
:
Importe o arquivo views.py
e adicione uma nova rota adicionando as linhas destacadas:
A nova visão que você acabou de criar será agora visível quando você visitar /
(quando a aplicação estiver em execução).
Salve e feche o arquivo.
Por fim, você precisará atualizar o INSTALLED_APPS
para incluir testsite
no settings.py
:
Aqui, você adiciona testsite
à lista de aplicativos em settings.py
para que o Django possa fazer algumas suposições sobre a estrutura do seu projeto. Neste caso, ele assumirá que a pasta templates
contém modelos do Django que você pode usar para renderizar visualizações.
A partir do diretório raiz do projeto (testsite
), inicie o servidor de desenvolvimento do Django com o seguinte comando, substituindo seu-endereço-de-servidor
pelo endereço IP do seu próprio servidor.
Abra um navegador e visite seu-endereço-de-servidor:8000
. A página deve se parecer com esta:
Neste ponto, a página exibe uma imagem de perfil de Sammy, o Tubarão. Abaixo da imagem está o texto Olá, Sammy! em script azul.
Para parar o servidor de desenvolvimento do Django, pressione CONTROL-C
.
Neste passo, você criou uma visualização básica que atua como a página inicial do seu projeto Django. Em seguida, você adicionará suporte para CSP à sua aplicação.
Passo 2 — Instalando o Middleware CSP
Neste passo, você instalará e implementará um middleware CSP para que possa adicionar cabeçalhos CSP e trabalhar com recursos CSP em suas visualizações. O Middleware adiciona funcionalidades adicionais a qualquer solicitação ou resposta que o Django manipula. Neste caso, o Middleware Django-CSP adiciona suporte CSP às respostas do Django.
Primeiro, você irá instalar o Middleware CSP da Mozilla no seu projeto Django usando pip
, o gerenciador de pacotes do Python. Use o seguinte comando para instalar o pacote necessário do PyPi, o Índice de Pacotes do Python. Para executar o comando, você pode parar o servidor de desenvolvimento do Django usando CONTROL-C
ou abrir uma nova aba no seu terminal:
Em seguida, adicione o middleware às configurações do seu projeto Django. Abra o arquivo settings.py
:
Com o django-csp
instalado, agora você pode adicionar o middleware no settings.py
. Isso adicionará cabeçalhos CSP às suas respostas.
Adicione a seguinte linha à matriz de configuração MIDDLEWARE
:
Salve e feche o arquivo quando terminar. Seu projeto Django agora suporta CSPs. No próximo passo, você começará a adicionar cabeçalhos CSP.
Passo 3 — Implementando um Cabeçalho CSP
Agora que seu projeto suporta CSPs, está pronto para ser fortalecido em termos de segurança. Para conseguir isso, você configurará o projeto para adicionar cabeçalhos CSP às suas respostas. Um cabeçalho CSP é o que diz ao navegador como se comportar quando encontra um determinado tipo de conteúdo. Portanto, se o cabeçalho disser para permitir apenas imagens de um domínio específico, então o navegador só permitirá imagens desse domínio.
Usando o nano ou o seu editor de texto favorito, abra o settings.py
:
Defina as seguintes variáveis em qualquer lugar do arquivo:
Essas regras são o modelo para a sua CSP. Essas linhas indicam quais fontes são permitidas para imagens, folhas de estilo e scripts, respectivamente. No momento, todas elas contêm a string 'self'
, o que significa que apenas recursos do seu próprio domínio são permitidos.
Salve e feche o arquivo quando terminar.
Execute seu projeto Django com o seguinte comando:
Ao visitar o-ip-do-seu-servidor:8000
, você verá que o site está quebrado:
Como esperado, a imagem não aparece e o texto aparece com o estilo padrão (negrito preto). Isso significa que o cabeçalho CSP está sendo aplicado, e nossa página está agora mais segura. Como a visualização que você criou anteriormente está referenciando folhas de estilo e imagens de domínios que não são seus, o navegador os bloqueia.
Seu projeto agora tem uma CSP funcional que está instruindo o navegador a bloquear recursos que não são do seu domínio. Em seguida, você modificará a CSP para permitir recursos específicos, o que corrigirá a falta de imagem e estilo na página inicial.
Passo 4 — Modificando a CSP para Permitir Recursos Externos
Agora que você tem um CSP básico, você irá modificá-lo com base no que está usando em seu site. Como exemplo, um site que usa Fontes da Adobe e vídeos incorporados do YouTube precisará permitir esses recursos. No entanto, se o seu site apenas exibe imagens de seu próprio domínio, você pode deixar as configurações de imagens com seus valores restritivos padrão.
O primeiro passo é encontrar todos os recursos que você precisa aprovar. Você pode usar as ferramentas de desenvolvedor do seu navegador para fazer isso. Abra o Monitor de Rede em Inspect Element, atualize a página e veja os recursos bloqueados:
O log de rede mostra que dois recursos estão sendo bloqueados pelo CSP: uma folha de estilos de fonts.googleapis.com e uma imagem de html.sammy-codes.com. Para permitir esses recursos no cabeçalho do CSP, você precisará modificar as variáveis em settings.py.
Para permitir recursos de domínios externos, adicione o domínio à parte do CSP que corresponde ao tipo de arquivo. Portanto, para permitir uma imagem de html.sammy-codes.com, você adicionará html.sammy-codes.com ao CSP_STYLE_SRC.
Abra settings.py e adicione o seguinte à variável CSP_STYLE_SRC:
Agora, em vez de apenas permitir imagens de seu domínio, o site também permite imagens de html.sammy-codes.com.
A visualização do índice utiliza Google Fonts. O Google fornece ao seu site as fontes (de https://fonts.gstatic.com
) e uma folha de estilos para aplicá-las (de https://fonts.googleapis.com
). Para permitir o carregamento das fontes, adicione o seguinte à sua CSP:
Similar a permitir imagens de html.sammy-codes.com
, você também permitirá folhas de estilos de fonts.googleapis.com
e fontes de fonts.gstatic.com
. Para contexto, a folha de estilos carregada de fonts.googleapis.com
é usada para aplicar as fontes. As próprias fontes são carregadas de fonts.gstatic.com
.
Salve e feche o arquivo.
Aviso: Similar a self
, existem outras palavras-chave como unsafe-inline
, unsafe-eval
, ou unsafe-hashes
que podem ser usadas em uma CSP. É altamente recomendado que você evite usar essas regras na sua CSP. Embora elas facilitem a implementação, podem ser usadas para contornar a CSP e torná-la inútil.
Para mais informações, veja a documentação do produto Mozilla para “Script inline inseguro”.
Agora, o Google Fonts estará autorizado a carregar estilos e fontes no seu site e html.sammy-codes.com
poderá carregar imagens. No entanto, ao visitar uma página no seu servidor, você pode perceber que apenas as imagens estão sendo carregadas agora. Isso ocorre porque os estilos embutidos no HTML que são usados para aplicar as fontes não são permitidos. Você corrigirá isso no próximo passo.
Passo 5 — Trabalhando com Scripts e Estilos Inline
Neste ponto, você modificou a CSP para permitir recursos externos. Mas recursos inline, como estilos e scripts em sua visualização, ainda não são permitidos. Neste passo, você os fará funcionar para que possa aplicar estilos de fonte.
Há duas maneiras de permitir scripts e estilos inline: nonces e hashes. Se você perceber que está frequentemente modificando scripts e estilos inline, use nonces para evitar alterações frequentes na sua CSP. Se raramente atualiza scripts e estilos inline, usar hashes é uma abordagem razoável.
Usando nonce
para Permitir Scripts Inline
Primeiro, você usará a abordagem nonce. Um nonce é um token gerado aleatoriamente que é único para cada solicitação. Se duas pessoas visitarem seu site, cada uma receberá um nonce
único que é incorporado nos scripts e estilos inline que você aprova. Pense em nonce como uma senha única que aprova certas partes de um site para serem executadas por uma única sessão.
Para adicionar suporte a nonces ao seu projeto, você atualizará sua CSP no arquivo settings.py
. Abra o arquivo para edição:
Adicione script-src
em CSP_INCLUDE_NONCE_IN
no arquivo settings.py
.
Defina CSP_INCLUDE_NONCE_IN
em qualquer lugar do arquivo e adicione 'script-src'
a ele:
CSP_INCLUDE_NONCE_IN
indica quais scripts em linha você está autorizado a adicionar atributos nonce
. CSP_INCLUDE_NONCE_IN
é tratado como uma matriz, já que várias fontes de dados suportam nonces (por exemplo, style-src
).
Salve e feche o arquivo.
Agora é permitido gerar nonces para scripts em linha quando você adiciona o atributo nonce
a eles em seu modelo de visualização. Para testar isso, você usará um snippet JavaScript simples.
Abra o index.html
para edição:
Adicione o seguinte snippet na <head>
do HTML:
Este snippet imprime Olá do console!
no console do navegador. No entanto, como seu projeto possui uma CSP que só permite scripts em linha se tiverem um nonce
, este script não será executado e, em vez disso, produzirá um erro.
Você pode ver este erro no console do seu navegador ao atualizar a página:
A imagem é carregada porque você permitiu recursos externos no passo anterior. Como esperado, o estilo é atualmente o padrão porque você ainda não permitiu estilos em linha. Também como esperado, a mensagem do console não foi impressa e retornou um erro. Você precisará fornecer um nonce
para aprová-lo.
Podes fazer isso adicionando nonce="{{request.csp_nonce}}"
a este script como um atributo. Abra o index.html
para edição e adicione a parte destacada conforme mostrado aqui:
Salve e feche o seu ficheiro quando terminar.
Se atualizares a página, o script agora será executado:
Quando olhares no Inspeccionar Elemento, vais notar que não há valor para o atributo:
O valor não aparece por motivos de segurança. O navegador já processou o valor. Está oculto para que quaisquer scripts com acesso ao DOM não possam acedê-lo e aplicá-lo a algum outro script. Se visualizares o código-fonte da página em vez disso, é isto que o navegador recebeu:
Repara que cada vez que atualizas a página, o valor do nonce
muda. Isso ocorre porque o middleware CSP no nosso projeto gera um novo nonce
para cada pedido.
Estes valores de nonce
são anexados ao cabeçalho CSP quando o navegador recebe a resposta:
Cada pedido que o navegador faz ao teu site terá um valor de nonce
único para esse script. Como o nonce
é fornecido no cabeçalho CSP, isso significa que o servidor Django aprovou esse script específico para ser executado.
Tu atualizaste o teu projeto para funcionar com nonce, o que pode ser aplicado a múltiplos recursos. Por exemplo, podes aplicá-lo também a estilos, atualizando CSP_INCLUDE_NONCE_IN
para permitir style-src
. Mas há uma abordagem mais simples para aprovar recursos em linha, e é isso que vais fazer a seguir.
Usando Hashes para Permitir Estilos Inline
Outra abordagem para permitir scripts e estilos inline é com hashes. Um hash é um identificador único para um recurso inline específico.
Como exemplo, este é o estilo inline em nosso modelo:
Atualmente, porém, os estilos não estão funcionando. Quando você visualiza o site no navegador, as imagens são carregadas com sucesso, mas as fontes e os estilos não são aplicados:
No console do navegador, você encontrará um erro indicando que um estilo inline viola a CSP. (Pode haver outros erros, mas procure pelo erro relacionado ao estilo inline.)
O erro é produzido porque o estilo não é aprovado pela nossa CSP. Mas, observe que o erro fornece o hash necessário para aprovar o trecho de estilo. Este hash é único para este trecho de estilo específico. Nenhum outro trecho terá o mesmo hash. Quando este hash é colocado dentro da CSP, toda vez que este estilo específico é carregado, ele será aprovado. No entanto, se você modificar esses estilos, precisará obter o novo hash e substituir o antigo por ele na CSP.
Agora você aplicará o hash adicionando-o a CSP_STYLE_SRC
em settings.py
, assim:
Adicionar o hash sha256-...
à lista CSP_STYLE_SRC
permitirá que o navegador carregue a folha de estilos sem erros.
Salve e feche o arquivo.
Agora, recarregue o site no navegador e as fontes e estilos devem carregar com sucesso:
Os estilos e scripts embutidos agora funcionam corretamente. Neste passo, você usou duas abordagens diferentes, nonces e hashes, para permitir estilos e scripts embutidos.
Mas, há um problema importante a ser abordado. CSPs são tediosos de manter, especialmente para sites grandes. Você pode precisar de uma maneira de rastrear quando o CSP bloqueia um recurso para poder determinar se é um recurso malicioso ou simplesmente uma parte quebrada do seu site. No próximo passo, você usará o Sentry para fazer log e acompanhar todas as violações produzidas pelo seu CSP.
Passo 6 — Reportando Violações com o Sentry (Opcional)
Dado o quão estritos os CSPs tendem a ser, é bom saber quando ele está bloqueando conteúdo — especialmente porque bloquear conteúdo provavelmente significa que alguma funcionalidade do seu site não funcionará. Ferramentas como o Sentry podem informá-lo quando o CSP está bloqueando solicitações para os usuários. Neste passo, você configurará o Sentry para fazer log e relatar violações do CSP.
Como pré-requisito, você se cadastrou em uma conta com o Sentry. Agora você criará um projeto.
No canto superior esquerdo do painel do Sentry, clique na guia Projetos:
No canto superior direito, clique no botão Criar Projeto:
Você verá uma série de logos com um título que diz Escolha uma plataforma. Escolha Django:
Em seguida, na parte inferior, nomeie seu projeto (para este exemplo, vamos usar sammys-tutorial
), e clique no botão Criar Projeto:
O Sentry fornecerá um trecho de código para adicionar ao seu arquivo settings.py
. Salve este trecho para adicionar em um passo posterior.
No seu terminal, instale o SDK do Sentry:
Abra o settings.py
assim:
Adicione o seguinte ao final do arquivo e certifique-se de substituir SENTRY_DSN
pelo valor do painel:
Este código é fornecido pelo Sentry para que possa registrar quaisquer erros que ocorram em sua aplicação. É a configuração padrão para o Sentry e inicializa o Sentry para registrar problemas em nosso servidor. Tecnicamente, você não precisa inicializar o Sentry em seu servidor para violações de CSP, mas no caso raro de haver algum problema na renderização de nonces ou hashes, esses erros serão registrados no Sentry.
Salve e feche o arquivo.
Em seguida, volte para o painel do seu projeto e clique no ícone de engrenagem para ir para Configurações:
Vá para a guia Cabeçalhos de Segurança:
Copie o report-uri
:
Adicione-o ao seu CSP assim:
Certifique-se de substituir your-report-uri
pelo valor que você copiou do painel.
Salve e feche seu arquivo. Agora, quando a aplicação da CSP causar uma violação, o Sentry a registrará neste URI. Você pode testar isso removendo um domínio ou hash do seu CSP, ou removendo o nonce
do script que você adicionou anteriormente. Carregue a página no navegador e você verá o erro na página de Problemas do Sentry:
Se você se sentir sobrecarregado pelo número de registros, também pode definir
CSP_REPORT_PERCENTAGE
em settings.py
para enviar apenas uma porcentagem dos registros para o Sentry.
Agora, sempre que houver uma violação da CSP, você será notificado e poderá ver o erro no Sentry.
Conclusão
Neste artigo, você protegeu sua aplicação Django com uma política de segurança de conteúdo. Você atualizou sua política para permitir recursos externos e usa nonces e hashes para permitir scripts e estilos inline. Você também a configurou para enviar violações para o Sentry. Como próximo passo, confira a documentação CSP do Django para aprender mais sobre como aplicar sua CSP.