Denis Zanin
Denis Zanin

entre ensaios e hiatos, um blog pessoal que aborda textos sobre jornalismo digital, segurança e privacidade na Internet; literatura, fotografia, política, etc. são alguns dos outros temas abordados.

Denis Zanin, programador e jornalista, especializado em segurança, jornalismo digital e privacidade na Internet. Literatura, cinema e fotografia estão também entre seus escritos favoritos.

Compartilhar


Tags


Denis Zanin

Configuração segura dos cabeçalhos no Nginx

Enquanto pesquisava sobre segurança digital, vasculhando vários sites, encontrei um projeto que analisa e avalia gratuitamente o nível de segurança de um site, baseando-se apenas nos HTTP response headers.

Nota: Este texto foi atualizado em 22/02/2020 para incluir dúvidas de outros usuários sobre a configuração dos cabeçalhos e a solução para estas dúvidas.

Enquanto pesquisava sobre segurança digital, vasculhando em vários sites pelo Google, encontrei aleatoriamente o blog de Scott Helme. Por este, descobri que é autor de um projeto de cybersecurity, cujo objetivo é analisar e avaliar gratuitamente o nível de segurança de um determinado site, baseando-se apenas nos cabeçalhos do servidor (HTTP response headers). O projeto está disponível em https://securityheaders.io/.

Ao realizar o teste por aqui, descobri que a minha configuração definida no Nginx estava com vários cabeçalhos mal configurados. Resolvi, então, escrever este tutorial de análise e correção dos cabeçalhos (somente para Nginx; no Google você encontrará os passo-a-passos para outros servidores).

Configuração

1. Análise

Acesse https://securityheaders.io/ e realize um teste, digitando o endereço do site que deseja analisar (selecione a opção Hide results para esconder a URL do site busca da página principal do projeto).

No meu primeiro teste, recebi uma nota D em Report Security Summary, ou seja, muito vulnerável a diversos ataques. O header HTTP Strict Transport Security (HSTS) era o único cabeçalho configurado corretamente por aqui, pois eu havia configurado SSL anteriormente, com um script de automação e configuração de servidor que criei (postarei isso depois no Github). O restante dos headers estavam todos vermelhos neste teste, nenhum configurado: Content-Security-Policy, X-Frame-Options, X-XSS-Protection, X-Content-Type-Options, Referrer-Policy, HTTP Public Key Pinning e Feature-Policy.

Content-Security-Policy e X-XSS-Protection são exemplos de headers que evitarão ataques, como o Cross site scripting (XSS) , e sua implementação ajudará a proteger os usuários do seu site, além dele próprio. Na CryptoRave de 2015 eu palestrei sobre a customização do Firefox, pensando na privacidade e segurança do usuário, e em uma parte dela comento sobre diversos ataques (e casos reais!) citados aqui, que são proferidos durante a navegação.

Se ficou curioso(a) sobre a palestra, o vídeo na íntegra está publicado no YouTube, e pode ser visto aqui (a fala dos ataques virtuais começa em 21m05s do vídeo):

Agora, voltando do desvio de assunto e indo ao tópico do post sobre a configuração... então, o quê fazer?

2. Configuração

Para começar, edite o arquivo de configuração do Nginx, localizado em /etc/nginx/nginx.conf. Lembrando que dependendo do seu sistema e estrutura organizacional, os arquivos de configuração poderão encontrar-se em diferentes diretórios.

O objetivo desse texto é priorizar apenas a configuração dos cabeçalhos, portanto suponho que o seu servidor Nginx está com o SSL e certificados devidamente configurados e funcionais.

a. HTTP Strict Transport Security (HSTS)

O HTTP Strict Transport Security, ou HSTS, é essencial para incluir em seu site. A funcionalidade fortalecerá a conexão entre usuário-site, adicionando uma camada extra de criptografia TLS nesta conexão, para usar HTTPS.

Caso ainda não esteja configurado em seu servidor, configure o HTTP Strict Transport Security. Acrescente a linha abaixo no arquivo:

add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload";

b. Content Security Policy

O Content Security Policy, ou CSP, é uma medida efetiva contra ataques Cross Site Scripting (XSS), através deste cabeçalho são definidas as fontes de conteúdos aprovados que serão carregados no site, para evitar (whitelisting) que o navegador acesse elementos não-autorizados. Exemplos deste ataque foram ditos no vídeo da palestra anexada acima.

Para isso, acrescente a linha abaixo ao arquivo de configuração do Nginx.

add_header Content-Security-Policy "default-src 'self'";

Ou, um exemplo mais completo na linha abaixo com maiores restrições (e que possivelmente irá quebrar toda a estrutura do site).[1]

add_header Content-Security-Policy "upgrade-insecure-requests; block-all-mixed-content; default-src 'self' https; style-src 'self'; img-src 'self'; script-src 'self'";

c. X-Frame Options

X-Frame Options dirá ao navegador se o site poderá, ou não, ser inserido dentro de um frame (interno, ou externo). Inicialmente, pode não fazer sentido, mas há diversos ataques com o nome de clickjacking rolando por aí. O roubo de clicks acontece quando o usuário clica em um botão ou link, sem intenção do clique. O valor SAMEORIGIN na linha abaixo, determinada que o frame será aceito se a origem (domínio) for a mesma que a do site.

add_header X-Frame-Options SAMEORIGIN;

Atualização sobre o uso do X-Frame Options (22/02/2020)

Como já citei, o X-Frame-Options dirá ao navegador se o site permite, ou não, ser inserido em um frame (ou iframe). Este header aceita dois valores: DENY, para bloquear toda requisição em frames; ou, como no exemplo, SAMEORIGIN.

Esta semana, o usuário David entrou em contato para tirar uma dúvida sobre um terceiro valor para o header X-Frame-Options: ALLOW-FROM. Este valor permitia especificar quais URIs poderiam abrir um frame; uma lista de domínios seguros e autorizados a requisitar frames. Mesmo definindo de forma correta na configuração do Nginx, David não estava conseguindo liberar seus URIs específicos.

O problema que encontramos foi que o valor ALLOW-FROM está obsoleto. De acordo com a documentação da Mozilla[2], headers definidos com este valor são ignorados pelos navegadores modernos. Os navegadores que não suportam este valor de cabeçalho, ALLOW-FROM são: Chrome (todas as versões), Firefox (versão 18-70), Opera (todas as versões), Safari (todas as versões), enquanto SAMEORIGIN é amplamente aceito pelos navegadores.[3]

Então... qual a solução?

Para resolver o problema de David, precisamos configurar um outro header: Content-Security-Policy. A descrição sobre este cabeçalho já foi detalhada neste texto, mas faltou dizer sobre um valor: o frame-ancestors.[4]

Para permitir requisições de frame, iframe, object, embed ou applet, de domínios específicos, não vamos usar o ALLOW-FROM, que está obsoleto, e vamos configurar o cabeçalho Content-Security-Policy com o valor frame-ancestors. Exemplo:

add_header Content-Security-Policy "frame-ancestors 'self' outrodominio.com;";

Lembrando que 'self' refere-se ao próprio domínio.
Ou, uma outra configuração possível:

add_header Content-Security-Policy "frame-ancestors seudominio.com outrodominio.com;";

Para incluir estes valores de configuração juntamente com outros valores do Content-Security-Policy, separe-os com um ponto-e-vírgula, ;, como no exemplo do item b deste texto.

O valor frame-ancestors é amplamente aceito pelos navegadores modernos com exceção do Internet Explorer.

Mas... o IE é um navegador moderno?
Deixo a pergunta no ar...


d. X-XSS-Protection

X-XSS-Protection dirá ao navegador para habilitar, ou não, o filtro de Cross-Site Scripting, ou seja, se o navegador do usuário poderá relacionar scripts entre outros domínios.

add_header X-XSS-Protection "1; mode=block";

e. X-Content-Type-Options

X-Content-Type-Options é o header para evitar que os navegadores obtenham uma leitura superficial do tipo de um arquivo. O "tipo de um conteúdo", o MIME, é avaliado previamente pelo navegador[5]. Exemplo: um referencial no site para uma stylesheet será avaliado pelo navegador, e este não será carregado a não ser que o tipo (MIME) seja compatível com "text/css".

add_header X-Content-Type-Options nosniff;

f. Referrer-Policy

Referrer-Policy é o header para controlar a informação que será enviada como referencial para outro link destino. Ou seja, o referencial origem num clique para o destino do clique. Para a especificação abaixo, restringimos o referencial: o referencial de origem, proveniente de https, de um clique será enviado para o destino https; a origem não será enviada se o destino for http, sem conexão segura.

add_header Referrer-Policy "no-referrer-when-downgrade";

g. HTTP Public Key Pinning (HPKP)

HTTP Public Key Pinning protege seu site de ataques MiTM, man-in-the-middle, quando há "alguém-no-meio-do-caminho", um Big Brother monitorando suas ações e simulando conexões seguras (ataques bem comuns em wi-fi públicas)[6].

As identidades dos certificados (ou as assinaturas digitais) são definidas explicitamente no cabeçalho, dizendo ao navegador do usuário quais certificados SSL o navegador deverá confiar naquele site, desta forma, ainda que exista "alguém-no-meio-do-caminho" monitorando as conexões seguras do usuário, as assinaturas digitais dos certificados estão claramente especificadas e o navegador não irá confiar naquela conexão.

Antes de acrescentar o cabeçalho ao Nginx é preciso recuperar a identidade dos certificados, sua assinatura digital. Para isso, substitua SEU_CERTIFICADO_AQUI, no comando abaixo, com o caminho do arquivo do seu certificado (o valor da opção ssl_certificate, definida no seu arquivo de configuração).

openssl x509 -in SEU_CERTIFICADO_AQUI -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

A execução desse comando resultará em algo como vrqHdf0qoWVMi01inbU4HLRBLBe9Lx2JCkyU12suxLA=, e que deverá ser acrescido ao código do cabeçalho abaixo. Este resultado é a assinatura do seu certificado em sha-256, e depois transformada em uma string em base64.

add_header Public-Key-Pins 'pin-sha256="RESULTADO_AQUI"; includeSubdomains; max-age=10';

E atenção: sempre que o seu certificado for renovado, este cabeçalho no Nginx deverá ser atualizado com a nova assinatura digital. Isso pode ser automatizado com um script, como fiz por aqui, ao gerar os certificados do Let's Encrypt.

h. Feature-Policy

Feature-Policy é o mais novo header, que permite ao site controlar quais funcionalidades e APIs poderão ser usadas pelo navegador do usuário. Na linha abaixo está um exemplo bem restritivo[7].

add_header Feature-Policy "geolocation 'none'; midi 'none'; notifications 'none'; push 'self'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; vibrate 'self'; fullscreen 'self'; payment 'self'";

Finalizando

Depois de acrescentar os 8 (oito!!) cabeçalhos ao arquivo de configuração do Nginx, execute o comando nginx -t para conferir se não há erros nos arquivos modificados.

Depois do teste de sintaxe, reinicie o serviço do servidor:

# Para ubuntu/ debian:
systemctl restart nginx.service

Agora realize novamente o teste de avaliação dos cabeçalhos em https://securityheaders.io/. Depois destas alterações descritas aqui, o resultado da avaliação foi A+.

E fim! ;-)

Notas de rodapé

  1. Em inglês, Content Security Policy (CSP)
    Quick Reference Guide
    , fonte externa. ↩︎

  2. Em inglês, MDN web docs: X-Frame-Options, fonte externa ↩︎

  3. A tabela completa de suporte dos navegadores está disponível na documentação do X-Frame-Options (em inglês), fonte externa ↩︎

  4. Em inglês, MDN web docs: CSP: frame-ancestors, fonte externa ↩︎

  5. Em inglês, Reducing MIME type security risks, fonte externa. ↩︎

  6. Em inglês, Optimising NGINX and Server Security, fonte externa. ↩︎

  7. Em inglês, A new security header: Feature Policy, fonte externa. ↩︎

Denis Zanin, programador e jornalista, especializado em segurança, jornalismo digital e privacidade na Internet. Literatura, cinema e fotografia estão também entre seus escritos favoritos.

Ver comentários