O autor selecionou o 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 utilizados para carregá-lo e renderizá-lo. Como exemplo, ao acessar https://www.digitalocean.com
, seu navegador baixa o HTML e CSS diretamente de digitalocean.com
. No entanto, as imagens e outros elementos são baixados de assets.digitalocean.com
, e os 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á todos eles. 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 servem as Políticas de Segurança de Conteúdo (CSPs).
Usando um cabeçalho CSP, um desenvolvedor pode permitir explicitamente que determinados recursos sejam executados, ao mesmo tempo que 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 CSP será mais seguro, pois garante que apenas recursos aprovados sejam permitidos.
Neste tutorial, você implementará um CSP em uma aplicação básica do Django. Você irá personalizar o CSP para permitir que determinados domínios e recursos inline sejam executados. Opcionalmente, você também pode usar o Sentry para registrar violações.
Pré-requisitos
Para completar 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 na série de tutoriais, Como Programar em Python e Desenvolvimento Django.
- Uma conta no Sentry para rastrear violações de CSP (opcional).
Passo 1 — Criando uma Visualização de Demonstração
Neste passo, você modificará como sua aplicação lida com visualizações para que possa adicionar suporte ao CSP.
Como pré-requisito, você instalou o Django e configurou um projeto de amostra. A visualização padrão no Django é muito simples para demonstrar todas as capacidades do middleware CSP, então você 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. Vamos chamá-lo 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ê adicionará uma visualização básica que 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 visualização que criamos renderizará esta página HTML simples. Ele exibirá o texto Olá, Sammy! junto com uma imagem de Sammy the Shark.
Salve e feche o arquivo quando terminar.
Para acessar esta visualização, você precisará atualizar urls.py
:
Importe o arquivo views.py
e adicione uma nova rota adicionando as linhas destacadas:
A nova visualização que você acabou de criar agora será visualizável quando você visitar /
(quando o aplicativo estiver em execução).
Salve e feche o arquivo.
Finalmente, você precisará atualizar INSTALLED_APPS
para incluir testsite
em 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 templates 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 your-server-ip
pelo endereço IP do seu próprio servidor.
Abra um navegador e visite your-server-ip:8000
. A página deve parecer semelhante a 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 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ê vai instalar o Middleware CSP da Mozilla em 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 em seu terminal:
Em seguida, adicione o middleware às configurações do seu projeto Django. Abra settings.py
:
Com o django-csp
instalado, agora você pode adicionar o middleware em 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 protegido. 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 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 básico para 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 seu-ip-do-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 agora está 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 possui 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á utilizando em seu site. Como exemplo, um site que utiliza Fontes da Adobe e vídeos do YouTube incorporados precisará permitir esses recursos. No entanto, se o seu site apenas exibir imagens do próprio domínio, você pode deixar as configurações de imagens com seus valores padrão restritivos.
O primeiro passo é encontrar todos os recursos que você precisa aprovar. Você pode usar as ferramentas de desenvolvedor do seu navegador para isso. Abra o Monitor de Rede no Inspetor de Elementos, atualize a página e observe os recursos bloqueados:
O log de Rede mostra que dois recursos estão sendo bloqueados pelo CSP: uma folha de estilo de fonts.googleapis.com e uma imagem de html.sammy-codes.com. Para permitir esses recursos no cabeçalho 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. Então, para permitir uma imagem de html.sammy-codes.com, você irá 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 permitir apenas imagens do 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 estilo para aplicá-las (de https://fonts.googleapis.com
). Para permitir o carregamento das fontes, adicione o seguinte ao seu CSP:
Similar à permissão para imagens de html.sammy-codes.com
, você também permitirá folhas de estilo de fonts.googleapis.com
e fontes de fonts.gstatic.com
. Para contexto, a folha de estilo 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: Assim como self
, existem outras palavras-chave como unsafe-inline
, unsafe-eval
, ou unsafe-hashes
que podem ser usadas em um CSP. É altamente recomendado que você evite usar essas regras em seu CSP. Embora isso facilite a implementação, elas podem ser usadas para contornar o CSP e torná-lo inútil.
Para mais informações, consulte a documentação do produto Mozilla para “Script inline inseguro”.
Agora, o Google Fonts poderá carregar estilos e fontes em seu site e html.sammy-codes.com
poderá carregar imagens. No entanto, ao visitar uma página em seu servidor, você pode notar que apenas as imagens estão sendo carregadas agora. Isso ocorre porque os estilos inline 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 o 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ê fará com que eles funcionem para poder 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 em seu 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 de 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ê aprovar. Pense no nonce como uma senha única que aprova certas partes de um site para serem executadas em uma única sessão.
Para adicionar suporte a nonce ao seu projeto, você atualizará seu CSP no 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 inline você está autorizado a adicionar atributos nonce
. CSP_INCLUDE_NONCE_IN
é tratado como um array, já que várias fontes de dados suportam nonces (por exemplo, style-src
).
Salve e feche o arquivo.
Agora é permitido gerar nonces para scripts inline quando você adiciona o atributo nonce
a eles em seu modelo de visualização. Para testar isso, você usará um trecho de código JavaScript simples.
Abra index.html
para edição:
Adicione o seguinte trecho na <head>
do HTML:
Este trecho imprime Olá pelo console!
no console do navegador. No entanto, como seu projeto tem uma CSP que só permite scripts inline se eles 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 quando 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 inline. Também como esperado, a mensagem do console não foi impressa e retornou um erro. Você precisará dar um nonce
para aprová-lo.
Você pode fazer isso adicionando nonce="{{request.csp_nonce}}"
a este script como um atributo. Abra index.html
para edição e adicione a parte destacada conforme mostrado aqui:
Salve e feche seu arquivo quando terminar.
Se você atualizar a página, o script agora será executado:
Quando você olha em Inspeção de Elementos, você 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. Ele está oculto para que quaisquer scripts com acesso ao DOM não possam acessá-lo e aplicá-lo a algum outro script. Se você Visualizar o código-fonte da página em vez disso, é isso que o navegador recebeu:
Observe que toda vez que você atualiza a página, o valor do nonce
muda. Isso ocorre porque o middleware CSP em nosso projeto gera um novo nonce
para cada solicitação.
Esses valores de nonce
são anexados ao cabeçalho CSP quando o navegador recebe a resposta:
Toda solicitação que o navegador fizer ao seu site terá um valor de nonce
exclusivo 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.
Você atualizou seu projeto para funcionar com nonce, que pode ser aplicado a vários recursos. Por exemplo, você pode aplicá-lo também a estilos, atualizando CSP_INCLUDE_NONCE_IN
para permitir style-src
. Mas há uma abordagem mais simples para aprovar recursos inline, e é isso que você fará em seguida.
Usando Hashes para Permitir Estilos Inline
Outra abordagem para permitir scripts e estilos inline é usando 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 estilos não são aplicados:
No console do navegador, você encontrará um erro informando que um estilo inline viola a CSP. (Pode haver outros erros, mas procure pelo erro sobre 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. Mas, se você modificar esses estilos, precisará obter o novo hash e substituir o antigo na CSP.
Agora você aplicará o hash adicionando-o ao CSP_STYLE_SRC
em settings.py
, assim:
Adicionar o hash sha256-...
à lista CSP_STYLE_SRC
permitirá que o navegador carregue a folha de estilo 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 em linha agora funcionam corretamente. Neste passo, você utilizou duas abordagens diferentes, nonces e hashes, para permitir estilos e scripts em linha.
Mas, há um problema importante a ser abordado. As CSPs são tediosas de manter, especialmente para sites grandes. Você pode precisar de uma maneira de rastrear quando a 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 registrar e acompanhar todas as violações produzidas pela sua CSP.
Passo 6 — Relatando Violações com o Sentry (Opcional)
Dado o quão restritas as CSPs costumam ser, é bom saber quando ela está bloqueando conteúdo — especialmente porque bloquear conteúdo provavelmente significa que alguma funcionalidade do seu site não funcionará. Ferramentas como Sentry podem avisá-lo quando a CSP está bloqueando solicitações para os usuários. Neste passo, você configurará o Sentry para registrar e relatar violações da CSP.
Como pré-requisito, você se inscreveu para uma conta com o Sentry. Agora você criará um projeto.
No canto superior esquerdo do painel do Sentry, clique na aba Projetos:
No canto superior direito, clique no botão Criar Projeto:
Você verá uma série de logotipos com um título que diz Escolha uma plataforma. Escolha Django:
Em seguida, na parte inferior, nomeie seu projeto (para este exemplo, usaremos 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 arquivo 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 ele possa registrar quaisquer erros que ocorram em sua aplicação. É a configuração padrão do Sentry e inicializa o Sentry para registrar problemas em nosso servidor. Tecnicamente, você não precisa inicializar o Sentry no seu servidor para violações de CSP, mas no caso raro de algum problema ao renderizar 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 acessar Configurações:
Vá para a aba Cabeçalhos de Segurança:
Copie o report-uri
:
Adicione-o ao seu CSP da seguinte forma:
Certifique-se de substituir your-report-uri
pelo valor que você copiou do painel.
Salve e feche o seu arquivo. Agora, quando a aplicação da CSP causar uma violação, o Sentry irá registrá-la nesta 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 com a quantidade de logs, também pode definir
CSP_REPORT_PERCENTAGE
no arquivo settings.py
para enviar apenas uma porcentagem dos logs para o Sentry.
Agora, sempre que houver uma violação da CSP, você será notificado e poderá visualizar 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 utiliza nonces e hashes para permitir scripts e estilos inline. Você também a configurou para enviar violações ao Sentry. Como próximo passo, confira a documentação do CSP do Django para aprender mais sobre como fazer valer sua CSP.