O Express.js é o framework web mais popular para desenvolvimento de aplicações web no Node.js. Ele vem com várias funcionalidades para tornar o desenvolvimento de API mais rápido e fácil.
No entanto, o Express falha quando se trata de segurança, não garantindo um nível adequado de proteção contra vulnerabilidades comuns. Felizmente, ele pode ser facilmente estendido graças a middlewares, um conjunto de funções executadas durante o processamento de requisições HTTP.
Helmet.js é uma tecnologia baseada em middleware que melhora a segurança protegendo os cabeçalhos HTTP retornados por um aplicativo Node.js. Aqui, você aprenderá o que é Helmet, por que precisa dele e como integrá-lo ao Node.js para proteger os cabeçalhos HTTP em um aplicativo Express.js. Abordaremos:
- O que é o Helmet?
- Por que você precisa do Helmet em um aplicativo Node.js?
- Configurando um projeto Express do Node.js
- Explorando a segurança do Express sem o Helmet
- Protegendo o Express com o Helmet
- Instalando o Helmet no seu projeto Express
- Configurando o Helmet no Express
- Explorando a segurança do Express com o Helmet
- Configurando cabeçalhos de segurança no Helmet
- O cabeçalho Content-Security-Policy
- O cabeçalho Referrer-Policy
- O cabeçalho Strict-Transport-Security
- O cabeçalho X-Content-Type-Options
- O cabeçalho X-Frame-Options
Vamos explorar o Helmet no Node.js!
O que é o Helmet?
Helmet.js é uma biblioteca de JavaScript de código aberto que ajuda a proteger sua aplicação Node.js definindo vários cabeçalhos HTTP. Ele age como um middleware para o Express e tecnologias similares, adicionando ou removendo automaticamente cabeçalhos HTTP para estar em conformidade com os padrões de segurança da web.
Embora não seja uma solução perfeita, o Helmet dificulta a exploração de vulnerabilidades conhecidas pelos invasores. Ele ajuda a proteger aplicativos Node.js Express contra ameaças de segurança comuns, como ataques de Cross-Site Scripting (XSS) e click-jacking.
O Helmet é especialmente útil porque os aplicativos Express não vêm com cabeçalhos de segurança HTTP prontos para uso. Isso explica por que o pacote npm do helmet tem mais de 2.000.000 de downloads semanais, enquanto seu repositório no GitHub conta com mais de 9,5 mil estrelas!
Agora, vamos aprofundar por que adotar o Helmet no Node.js é tão importante.
Por que você precisa do Helmet em um aplicativo Node.js?
Sem o Helmet, os cabeçalhos padrão retornados pelo Express expõem informações sensíveis e tornam seu aplicativo Node.js vulnerável a atores mal-intencionados. Em contraste, usar o Helmet no Node.js protege seu aplicativo contra ataques XSS, vulnerabilidades de Política de Segurança de Conteúdo e outros problemas de segurança.
Vamos explorar essa questão ainda mais por meio de um exemplo. Você vai configurar um aplicativo Node.js Express e ver qual nível de segurança seus cabeçalhos HTTP padrão oferecem.
Configurando um projeto Node.js Express
Primeiro, certifique-se de ter o Node.js e o npm instalados em sua máquina. Se ainda não estiver configurado, comece baixando o Node.js e siga o assistente de instalação para configurá-lo.
Agora, vamos configurar um projeto Express. Execute o comando abaixo para criar a pasta do projeto express-demo:
mkdir express-demo
Em seguida, acesse a pasta express-demo no terminal, executando o seguinte comando:
cd express-demo
Execute o seguinte comando para inicializar um projeto npm padrão:
npm init -y
O comando npm init configurará um projeto npm em branco para você. Observe que a flag -y responderá automaticamente "sim" a todas as perguntas que o npm faria durante o processo.
Você precisa adicionar o Express às dependências do seu projeto. Instale o módulo Express com o comando abaixo:
npm install express
Em seguida, crie um arquivo index.js na pasta express-demo e inicialize-o com essas linhas:
// index.js const express = require("express") // criando uma instância do Express const app = express() const PORT = process.env.PORT || 3000 // inicializando uma API básica que // retorna a mensagem "Olá, mundo!" app.get("/", (req, res) => { res.json("Olá, mundo!") }) // executando o servidor app.listen(PORT, () => { console.log(Iniciando servidor Express em http://localhost:${PORT}) })
Isso não passa de uma configuração básica de um servidor Express com um único endpoint.
Inicie o servidor com este comando:
node index.js
O servidor Express agora deve estar em execução em http://localhost:3000. Execute o comando abaixo no terminal para testar o endpoint /:
curl http://localhost:3000/
Isso deve imprimir a seguinte saída:
"Olá, mundo!"
Agora você tem um aplicativo Express funcionando!
Explorando a segurança do Express sem o Helmet
Agora, vamos usar o aplicativo demo que acabamos de criar para verificar o comportamento padrão do Express em relação aos cabeçalhos de segurança HTTP.
Repita o comando curl acima com a flag --include para também obter os cabeçalhos de resposta HTTP:
curl http://localhost:3000/ --include
Se você não tiver o cURL instalado em seu computador, use um cliente HTTP, como o Postman ou Insomnia, para inspecionar os cabeçalhos HTTP da resposta.
Na seção de cabeçalho de resposta, você deve ver o seguinte:
X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 15 ETag: W/"f-pob1Yw/KBE+3vrbZz9GAyq5P2gE" Date: Fri, 20 Fev 2023 16:29:40 GMT Connection: keep-alive Keep-Alive: timeout=5
Observe o cabeçalho X-Powered-By. Como todos os cabeçalhos que começam com X-, ele é um cabeçalho não padrão. Especificamente, as tecnologias de backend geralmente usam X-Powered-By para indicar o nome e a versão do framework ou biblioteca usada pelo servidor para gerar a resposta HTTP.
Como recomendado pela OWASP, uma organização sem fins lucrativos que trabalha para melhorar a segurança na web, o X-Powered-By deve ser omitido. Isso ocorre porque você nunca deve dar detalhes sobre sua pilha de tecnologia para atacantes. Caso contrário, eles poderiam usar essas informações para explorar vulnerabilidades conhecidas naquele framework ou biblioteca.
Agora, vamos verificar o que o projeto Security Headers tem a dizer sobre o nível de segurança do seu aplicativo Express.
Como o Security Headers permite testar apenas endereços públicos, você precisa tornar o servidor Express local publicamente disponível. Você pode implantar seu aplicativo Express em um servidor ou aproveitar o ngrok, que permite expor um servidor da web local para a Internet.
Baixe o ngrok, extraia-o e abra a pasta no terminal. Em seguida, execute o seguinte comando para hospedar seu servidor local com o ngrok:
ngrok http 3000
Você deve obter um resultado semelhante ao abaixo:
O campo "Forwarding" contém a URL para acessar publicamente o seu servidor local. Como o endpoint a ser testado é /, copie a URL, adicione um caractere / no final e cole-a na entrada do Security Headers.
Após executar a inspeção você pode ver que, o projeto Security Headers atribui à aplicação demo uma nota preocupante "F". O motivo é que a resposta da API desenvolvida com Express sem o uso do Helmet não contém os cabeçalhos de segurança HTTP mais relevantes.
Por isso, a página oficial "Melhores Práticas de Produção: Segurança" do Express menciona a adoção do Helmet como uma das melhores práticas para aplicativos em produção. Vamos agora aprender como integrar o Helmet no Node.js!
Segurança do Express com Helmet
Vamos expandir o projeto de demonstração do Express implementado anteriormente para protegê-lo com o Helmet. Como você está prestes a aprender, isso só leva algumas linhas de código.
Instalando o Helmet no seu projeto Express
Primeiro, você precisa adicionar o Helmet.js às dependências do seu projeto. Instale o pacote npm helmet com o seguinte comando:
npm i helmet
Seu arquivo package.json agora conterá helmet no campo de dependências.
Configurando o Helmet no Express
Integrar o Helmet no seu aplicativo Node.js Express é simples. Em caso de problemas, siga o guia oficial.
Em seu arquivo index.js, importe o helmet com o seguinte comando:
const helmet = require("helmet")
Agora, registre o helmet em seu aplicativo Express com o abaixo:
app.use(helmet())
Lembre-se de que helmet() nada mais é do que um middleware Express. Especificamente, a função helmet() de nível superior é um invólucro de 15 sub-middlewares. Portanto, ao registrar helmet(), você está adicionando 15 middlewares do Express ao seu aplicativo.
Observe que cada middleware cuida de definir um cabeçalho de segurança HTTP.
Seu arquivo index.js agora será assim:
// index.js const express = require("express") const helmet = require("helmet") const PORT = process.env.PORT || 3000 const app = express() // habilitando o middleware Helmet app.use(helmet()) // inicializando uma API básica que // retorna a mensagem "Olá, mundo!" app.get("/", (req, res) => { res.json("Olá, mundo!") }) // executando o servidor app.listen(PORT, () => { console.log(Iniciando servidor Express em http://localhost:${PORT}) })
Isso é tudo! Adicionar o Helmet ao seu aplicativo Node.js envolve apenas duas linhas de código!
Explorando a segurança do Express com Helmet
Vamos repetir o teste de segurança realizado anteriormente. Pare o servidor local do Node.js e o reinicie com este comando:
node index.js
Repita o comando curl ou use um cliente HTTP para ver os cabeçalhos HTTP da nova resposta:
curl http://localhost:3000/ --include
A seção de cabeçalhos HTTP da resposta agora incluirá o seguinte:
Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests Cross-Origin-Embedder-Policy: require-corp Cross-Origin-Opener-Policy: same-origin Cross-Origin-Resource-Policy: same-origin X-DNS-Prefetch-Control: off X-Frame-Options: SAMEORIGIN Strict-Transport-Security: max-age=15552000; includeSubDomains X-Download-Options: noopen X-Content-Type-Options: nosniff Origin-Agent-Cluster: ?1 X-Permitted-Cross-Domain-Policies: none Referrer-Policy: no-referrer X-XSS-Protection: 0 Content-Type: application/json; charset=utf-8 Content-Length: 15 ETag: W/"f-pob1Yw/KBE+3vrbZz9GAyq5P2gE" Date: Fri, 20 Jan 2023 18:15:32 GMT Connection: keep-alive Keep-Alive: timeout=5
Como você pode ver, há muitos novos cabeçalhos HTTP na resposta. Além disso, X-Powered-By foi removido.
Se você desligou o ngrok, execute-o novamente com o seguinte comando:
ngrok http 3000
Caso contrário, visite o site do Security Headers imediatamente. Assim como antes, copie a URL fornecida pelo ngrok e clique em "Scan". Desta vez, o resultado será diferente!
Observe que agora a resposta da API inclui todos os principais cabeçalhos de segurança HTTP, exceto o Permissions-Policy. Isso permitiu que o aplicativo Express passasse de uma classificação "F" para uma classificação "A". Que grande mudança com apenas duas linhas de código!
Além disso, conforme mencionado no GitHub por um dos principais desenvolvedores do Helmet.js, o Helmet não suporta automaticamente o Permissions-Policy apenas porque a especificação do cabeçalho ainda está em estado de rascunho. Portanto, isso pode mudar em breve.
Configurando cabeçalhos de segurança no Helmet
Como você acabou de aprender, o Helmet define muitos cabeçalhos por padrão. Como as políticas de segurança mudam ao longo do tempo, é fundamental manter o helmet sempre atualizado. Dessa forma, os cabeçalhos de segurança introduzidos pelo Helmet sempre estarão em conformidade com os padrões de segurança.
Todos esses cabeçalhos têm impacto na segurança de sua aplicação, mas alguns são mais relevantes do que outros. Vamos agora nos concentrar nos cabeçalhos de segurança mais importantes para entender por que são úteis, como o Helmet os trata por padrão e como você pode configurá-los com o Helmet.
O cabeçalho Content-Security-Policy
Content Security Policy, também conhecido como CSP, é uma medida de segurança que ajuda a mitigar vários ataques, como cross-site scripting (XSS) e ataques de injeção de dados.
Especificamente, o CSP permite que você especifique de quais fontes de conteúdo uma página da web é permitida a carregar e executar. Por exemplo, você pode usar o CSP para bloquear uma página da web de carregar imagens e iframes de outros sites.
Você pode configurar o CSP por meio do cabeçalho HTTP Content-Security-Policy. Por padrão, o Helmet dá ao cabeçalho Content-Security-Policy o seguinte valor:
Content-Security-Policy: default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests
Com esta política, suas páginas da web não podem carregar fontes ou estilos remotos. Isso ocorre porque font-src e style-src são definidos como self, respectivamente. A política CSP definida pelo Helmet por padrão é muito restritiva, mas você pode alterá-la com o contentSecurityPolicy() da seguinte forma:
// sobrepondo "font-src" e "style-src" enquanto // mantém os outros valores padrão helmet.contentSecurityPolicy({ useDefaults: true, directives: { "font-src": ["'self'", "external-website.com"], // permitindo estilos de qualquer site "style-src": null, }, })
useDefaults aplica os valores padrão. Em seguida, as seguintes diretivas substituem os valores padrão. Defina useDefaults como false para definir uma política CSP do zero.
O cabeçalho Referrer-Policy
O cabeçalho HTTP Referrer-Policy define quais dados devem ser enviados como informações de referência no cabeçalho Referer. Por padrão, o Referer geralmente contém a URL atual a partir da qual uma solicitação HTTP é realizada.
Por exemplo, se você clicar em um link para um site de terceiros, o Referer conterá o endereço da página da web atual. Como resultado, o site de terceiros pode usar o cabeçalho para rastrear você ou entender o que você estava visitando.
Se o endereço atual contiver informações privadas do usuário, o site de terceiros poderá roubá-las do cabeçalho Referer. Por causa disso, embora esse cabeçalho seja tipicamente usado para cache ou análises, ele abre algumas preocupações de privacidade porque pode vazar informações sensíveis.
O Helmet define como no-referrer por padrão. Isso significa que o cabeçalho Referer sempre será vazio. Portanto, solicitações realizadas por páginas da web servidas pelo seu aplicativo Node.js não incluirão nenhuma informação de referência.
Se você quiser mudar essa política restritiva, você pode fazer isso com refererPolicy como abaixo:
// definindo "Referrer-Policy" como "no-referrer" app.use( helmet.referrerPolicy({ policy: "no-referrer", }) )
O cabeçalho Strict-Transport-Security
O cabeçalho Strict-Transport-Security HTTP, também conhecido como HSTS, especifica que um site ou recurso só pode ser acessado via HTTPS. Em detalhes, o parâmetro maxAge define o número de segundos que os navegadores devem se lembrar de preferir HTTPS em vez de HTTP.
Por padrão, o Helmet define o cabeçalho Strict-Transport-Security da seguinte maneira:
max-age = 15552000; includeSubDomains
Observe que 15552000 segundos correspondem a 180 dias, e includeSubDomains estende a política HSTS para todos os subdomínios do site.
Você pode configurar o cabeçalho Strict-Transport-Security com a função hsts do Helmet da seguinte maneira:
app.use( helmet.hsts({ // 60 dias maxAge: 86400, // removendo a opção "includeSubDomains" includeSubDomains: false, }) )
O cabeçalho X-Content-Type-Options
O cabeçalho HTTP X-Content-Type-Options define que os tipos MIME usados no cabeçalho Content-Type devem ser seguidos. Isso mitiga a técnica de sniffing de tipo MIME, que pode levar a ataques XSS e causar outras vulnerabilidades.
Por exemplo, invasores podem esconder código HTML em um arquivo .png. O navegador pode usar a técnica de sniffing de tipo MIME para determinar o tipo de conteúdo do recurso.
Como o arquivo contém código HTML, o navegador irá determinar que ele é um arquivo HTML em vez de uma imagem JPG. Assim, o navegador irá executar o código do invasor quando renderizar a página.
Por padrão, o Helmet define X-Content-Type-Options como nosniff. Isso desabilita e previne o sniffing de tipo MIME.
Observe que a função noSniff() contida em helmet() não aceita parâmetros. Se você quiser desabilitar esse comportamento e ir contra as políticas recomendadas de segurança, você pode impedir o Helmet de importar noSniff() com o seguinte código:
app.use( // desabilita o middleware noSniff() helmet({ noSniff: false, }) )
O cabeçalho X-Frame-Options
O cabeçalho de resposta HTTP X-Frame-Options especifica se um navegador deve ou não ser permitido a renderizar uma página nos elementos HTML <frame>, <iframe>, <embed> e <object>.
Ao evitar que o conteúdo do seu site seja incorporado em outros sites, você pode evitar ataques de click-jacking. Um ataque de click-jacking envolve enganar os usuários para clicar em algo diferente do que eles percebem ou esperam.
Por padrão, o Helmet define o X-Frame-Options para SAMEORIGIN. Isso permite que uma página da web seja incorporada em um quadro em páginas com a mesma origem que a própria página. Você pode definir este cabeçalho no Helmet com frameguard() da seguinte forma:
// definindo "X-Frame-Options" para "DENY" app.use( helmet.frameguard({ action: "deny", }) );
Se você deseja omitir completamente o cabeçalho X-Frame-Options, pode desativar o middleware frameguard() com o seguinte:
app.use( // não incluindo o middleware frameguard() helmet({ frameguard: false, }) )
Observe que isso não é recomendado por motivos de segurança.
Conclusão
Neste artigo, você aprendeu o que é o Helmet.js e por que ele desempenha um papel tão importante quando se trata de garantir a segurança de um aplicativo Node.js.
Como visto aqui, as APIs desenvolvidas em Express não envolvem cabeçalhos HTTP de segurança. Portanto, aplicativos Express padrão apresentam algumas preocupações de segurança.
Com o Helmet, você pode adicionar uma camada de segurança ao Node.js com apenas uma única linha de código, protegendo seu aplicativo contra os ataques e vulnerabilidades mais comuns.