Skip to main content

Implicações de segurança do compartilhamento de recursos entre origens (CORS) no Node.js

Escrito por
Headshot of Victor Ikechukwu

Victor Ikechukwu

blog feature cors

13 de setembro de 2023

0 minutos de leitura

Em aplicações web modernas, o compartilhamento de recursos entre origens (CORS) permite a comunicação segura entre aplicações hospedadas em origens diferentes. Os desenvolvedores usam o CORS para acessar serviços de outras aplicações em suas próprias aplicações. Essa abordagem elimina a necessidade de recriar funcionalidades do zero, acelerando o desenvolvimento e melhorando a experiência de quem desenvolve.

Embora o CORS seja útil, uma implementação incorreta pode expor suas aplicações Node.js a riscos de segurança, como violações de dados e acesso não autorizado por sites de terceiros. Configurações incorretas podem revelar dados confidenciais a origens não previstas ou permitir que sites maliciosos burlem as proteções da política de mesma origem (SOP).

Entender essas vulnerabilidades e adotar boas práticas para implementar o CORS com segurança ajuda a reduzir esses riscos e a manter a funcionalidade da sua aplicação.

Neste artigo, primeiro vamos explicar o que é CORS e apresentar alguns casos de uso. Em seguida, usaremos um exemplo de código para implementar CORS em uma aplicação Node.js. Depois de explorar os possíveis problemas de segurança, vamos revisar as boas práticas de uso do CORS e testar a segurança do nosso exemplo. Para acompanhar, você só precisa ter alguma experiência com JavaScript e Node.js.

Entenda o CORS e seus casos de uso

Os navegadores usam um mecanismo de segurança chamado política de mesma origem (SOP) para controlar como as aplicações web interagem. A SOP impede que aplicações hospedadas em uma origem leiam os recursos de uma aplicação em outra origem. 

Esse mecanismo impede que sites maliciosos leiam os dados de outros sites, mas também pode restringir usos legítimos. E se você quiser incluir dados meteorológicos na sua aplicação? Ou incorporar um vídeo do YouTube em uma página da web? Esses recursos públicos deveriam estar disponíveis para todos, mas a SOP os bloqueia.

O CORS permite que aplicações web contornem as restrições da SOP, viabilizando a comunicação entre diferentes serviços web e permitindo solicitações entre origens. As aplicações podem usar o método HTTP OPTIONS para enviar solicitações de preflight ao recurso na outra origem. Com essas solicitações, os navegadores verificam se o servidor da outra origem permite esse tipo de solicitação. Com base nessa informação, o navegador ignora ou aplica a SOP.

Entre os casos de uso comuns do CORS estão carregar recursos de redes de distribuição de conteúdo (CDNs), solicitar APIs em vários domínios e facilitar integrações com terceiros.

No entanto, implementar o CORS incorretamente na sua aplicação traz riscos de segurança. Você pode deixar seu app vulnerável a um ataque de falsificação de solicitação entre sites (CSRF), que induz usuários a realizar ações indesejadas na aplicação. Além disso, configurações permissivas demais podem permitir acesso não autorizado a dados de usuários e informações confidenciais entre origens, causando violações de dados ou expondo vulnerabilidades que agentes mal-intencionados podem explorar.

Por exemplo, você pode se sentir tentado a usar o curinga permitir todos, representado por um asterisco (*), como valor do cabeçalho HTTP Access-Control-Allow-Origin. Essa configuração de CORS permite que qualquer origem interaja com sua aplicação, abrindo caminho para possíveis vulnerabilidades. Em vez disso, opte por configurações específicas e restritivas. Para definir dinamicamente as origens permitidas, considere usar variáveis de ambiente como uma alternativa mais segura.

Como implementar o CORS com segurança em uma aplicação Node.js

Nesta seção, vamos mostrar como implementar o CORS com segurança em uma aplicação Node.js. Usaremos uma aplicação Node.js simples que adota o Express.js como framework web. Ela funcionará como uma API para uma livraria online hipotética. 

Embora a API tenha vários endpoints (para fetch, add, update e delete livros), este exemplo vai implementar apenas a busca de livros. Para simplificar, a API vai usar o objeto sampleBooksData como sistema de armazenamento de dados.

Pré-requisitos

Para acompanhar este tutorial, você precisa de:

  • Experiência com JavaScript

  • Node.js (recomendamos a versão 18.16.1)

  • Um navegador web moderno

Crie a aplicação Node.js

Para criar a aplicação, primeiro crie um novo diretório. Depois, dentro dele, execute o comando abaixo para iniciar uma nova aplicação Node.js:

npm init -y

Em seguida, execute o comando a seguir para instalar as dependências deste projeto:

npm i express cors

Por fim, crie um arquivo JavaScript chamado index.js. Cole nele o código abaixo:

// Import required modules
const express = require("express");
const cors = require("cors");

// Initialize the Express application
const app = express();
app.use(express.json());

//Sample object that will serve as a database to store data
let sampleBooksData = {
  books: [
    {
      id: 1,
      title: "Harry Potter",
    },
    {
      id: 2,
      title: "The Da Vinci Code",
    },
    {
      id: 3,
      title: "Twilight",
    },
  ],
};

app.get("/books", cors(), (req, res, next) => {

  try {
    res.status(200).json(sampleBooksData);
  } catch (error) {
    console.error(`Error while reading from DB ${error}`);
    res.status(500).json({ message: "Internal Server Error" });
  }

});

// Start server on port defined by the variable PORT
let port = 3000;
app.listen(port, () =>
  console.log(`Server running at http://localhost:${port}`)
);

O Express.js disponibiliza funções de middleware para processar solicitações e respostas na aplicação. Essas funções podem interceptar e modificar solicitações recebidas ou respostas enviadas, permitindo funcionalidades como registro de logs, autenticação ou, no nosso caso, configuração do CORS. O exemplo acima importa o módulo CORS usando const cors = require("cors");. Em seguida, aplica o módulo como função de middleware à rota /books com o código abaixo:

app.get("/books", cors(), (req, res, next) => {
...
} 

No entanto, a implementação atual do middleware cors na rota /books não especifica quais origens podem fazer uma solicitação GET. Assim, a configuração permite que todas as origens façam solicitações para esse caminho. Por motivos de segurança, essa não é uma boa prática, mas você pode configurar regras mais restritivas para o ambiente de produção.

Configure o CORS

Com quatro configurações diferentes, você pode implementar o CORS com mais segurança usando o middleware cors.

Ative o CORS para origens específicas

Você pode definir a opção origin nas configurações do CORS para escolher as origens permitidas. Essa configuração aumenta a segurança ao restringir o acesso a fontes confiáveis e impedir solicitações entre origens não autorizadas. O exemplo abaixo permite solicitações apenas de `https://example.com`:

const corsOptions = {
  origin: 'https://example.com'
};
app.get('/books', cors(corsOptions), (req,res) => { /* ... */ });

Configure os métodos HTTP permitidos

Você também pode definir os métodos permitidos usando a opção methods. Essa abordagem limita possíveis vetores de ataque ao permitir apenas as ações necessárias nos endpoints da API. O exemplo abaixo permite apenas ações GET e POST:

corsOptions.methods = ['GET', 'POST'];

Defina cabeçalhos personalizados e cabeçalhos expostos

Outra opção é configurar o cabeçalho CORS Access-Control-Allow-Headers usando a opção allowedHeaders e Access-Control-Expose-Headers com a opção exposedHeaders. Essa abordagem permite que a aplicação trate dados confidenciais corretamente e mantenha uma comunicação flexível entre os componentes do cliente e do servidor. O exemplo abaixo permite apenas os cabeçalhos Content-Type e Authorization e expõe somente X-Custom-Header:

corsOptions.allowedHeaders = ['Content-Type', 'Authorization'];
corsOptions.exposedHeaders = ['X-Custom-Header'];

Configure solicitações de preflight e o cache

Por fim, você pode ativar o cache de solicitações de preflight (OPTIONS) definindo a duração do cache pela configuração maxAge. Isso permite que o cliente armazene em cache as informações da política CORS do servidor durante um período determinado. Em solicitações subsequentes, os clientes podem usar os dados armazenados em cache, sem precisar buscá-los novamente no servidor.

Definir um valor adequado para maxAge reduz o tempo de resposta e o tráfego de rede, otimizando o desempenho e mantendo as políticas CORS atualizadas. O exemplo abaixo define maxAge como 86400 segundos (24 horas):

// Cache duration in seconds.
// Set maxAge to a high value (e.g., 86400) for long-lived caches.
// Default is no-cache.
const DAY_IN_SECONDS=86400;
corsOptions.maxAge=DAY_IN_SECONDS;

Agora que configuramos as opções, podemos usar nosso código como middleware em qualquer rota que precise ter o CORS ativado:

app.get('<route-of-your-choice>', cors(corsOptions), (req,res) => { /* ... */ });

Embora o CORS ajude a proteger a API, ele ainda é uma dependência de terceiros. Precisamos garantir que ele e as demais dependências incluídas no projeto sejam seguros e não tenham vulnerabilidades conhecidas. O Snyk Open Source, por exemplo, ajuda a verificar problemas de segurança em pacotes durante o desenvolvimento ou em pipelines de integração e entrega contínuas (CI/CD).

Implicações de segurança do CORS e boas práticas

Se não configurarmos o CORS corretamente, corremos o risco de provocar as vulnerabilidades de segurança mencionadas anteriormente. Em especial, podemos expor dados confidenciais a origens não previstas, permitir ações não autorizadas por sites maliciosos (ataques CSRF) e até possibilitar que invasores burlem as proteções da SOP com técnicas como cross-site scripting (XSS).

Para evitar essas situações, siga sempre as boas práticas ao configurar o CORS. Você também pode adotar outras medidas de segurança, como implementar cabeçalhos fora do CORS, assunto que veremos mais adiante nesta seção.

Boas práticas

Boas práticas como restringir as origens permitidas, usar cookies e tokens seguros e limitar os cabeçalhos expostos são essenciais para proteger suas aplicações e seus usuários. A seguir, vamos apresentar essas práticas com exemplos de código para mostrar como aplicá-las.

Restrinja as origens permitidas a uma lista de permissões

Para evitar o curinga que permite todas as origens (*) no objeto corsOptions, especifique as origens em uma lista de permissões:

const allowedOrigins = ['https://example.com', 'https://another-example.com'];
corsOptions.origin = (origin, callback) => {
  if (allowedOrigins.includes(origin)) {
    callback(null, true);
  } else {
    callback(new Error('Not allowed by CORS'));
  }
};

Use cookies e tokens seguros para autenticação

Use o protocolo HTTPS para garantir que sua aplicação transmita cookies e tokens com segurança. Use também bibliotecas de autenticação confiáveis, como Passport.js, ou soluções baseadas em JSON Web Token (JWT):

corsOptions.credentials = true;

app.use(passport.initialize());
passport.use(new JwtStrategy(/* ... */));
app.get('/books', cors(corsOptions), passport.authenticate('jwt', { session: false }), (req,res) => { /* ... */ });

Neste código, definir o valor de credentials to true permite que solicitações entre origens incluam credenciais.

Limite os cabeçalhos expostos e os métodos HTTP

Para especificar apenas os cabeçalhos essenciais, use a opção exposedHeaders. Além disso, use a opção methods na configuração para restringir os métodos com CORS ativado:

// Exposed Headers.
corsOptions.exposedHeaders = ['Content-Type', 'Authorization'];

// Permitted HTTP Methods
corsOptions.methods=['GET'];

Implemente cabeçalhos de segurança além do CORS

Você pode implementar cabeçalhos de segurança adicionais, além do CORS, para proteger ainda mais sua aplicação. Cabeçalhos de segurança HTTP são cabeçalhos que indicam aspectos de segurança da comunicação HTTP entre um cliente e um servidor.

O Helmet.js oferece um conjunto de cabeçalhos de segurança com valores padrão adequados. No entanto, é necessário configurar a política de segurança de conteúdo (CSP) para habilitar as funcionalidades definidas pela configuração do CORS. Siga as etapas abaixo para configurá-la.

Primeiro, execute o comando abaixo na linha de comando do diretório da sua aplicação Node.js para instalar o Helmet:

npm i helmet

Em seguida, importe o pacote para sua aplicação usando o código abaixo:

const helmet = require('helmet');
app.use(helmet());

Depois, configure a CSP para corresponder às suas configurações de CORS, conforme mostrado abaixo:

const cspConfig = {
  directives: {
    defaultSrc: ["'self'", 'https://example.com'],
    // ...other CSP directives matching your app's needs.
  }
};
app.use(helmet.contentSecurityPolicy(cspConfig));

Por fim, verifique seu trabalho. É fácil deixar passar uma configuração ou cometer um erro de digitação que comprometa a segurança da aplicação, especialmente à medida que ela cresce. O Snyk Code e as extensões das ferramentas para desenvolvedores da Snyk, como a extensão do Visual Studio Code, identificam cabeçalhos de segurança ausentes ou configurados incorretamente na sua base de código. Usar essas ferramentas de terceiros para resolver proativamente esses problemas de segurança durante o desenvolvimento ajuda a manter sua aplicação Node.js segura.

Teste implementações e a segurança do CORS

Para garantir que sua aplicação esteja protegida contra possíveis ameaças, teste a implementação do CORS e sua segurança. Essa abordagem ajuda a identificar possíveis vulnerabilidades e configurações incorretas. Os testes também confirmam que as regras de CORS funcionam como esperado e não expõem a aplicação a riscos de segurança, como violações de dados ou acesso não autorizado por sites de terceiros.

Há várias maneiras de testar implementações e a segurança do CORS. A seguir, vamos apresentar alguns métodos populares.

Ferramentas de desenvolvedor do navegador

Os navegadores modernos oferecem ferramentas de desenvolvedor para inspecionar e depurar aplicações web. Você pode usá-las para testar o CORS examinando solicitações e respostas de rede. Simule solicitações entre origens para verificar se as regras de CORS as bloqueiam ou permitem corretamente.

Mais adiante, vamos aprofundar esse método aplicando-o à aplicação Node.js de exemplo que criamos anteriormente.

Postman

Você pode usar o popular Postman, uma ferramenta de desenvolvimento e teste de APIs, para testar o CORS. Com ele, você pode criar solicitações HTTP e especificar cabeçalhos personalizados para testar configurações de CORS. O Postman também pode gerar automaticamente solicitações de preflight para testar o cache dessas solicitações. Use também os recursos de scripting do Postman para automatizar cenários de teste.

Scripts personalizados

Você pode criar scripts personalizados para testar configurações de CORS. Por exemplo, use uma linguagem de programação como Python ou JavaScript para enviar solicitações HTTP e verificar se as respostas incluem cabeçalhos CORS. Você também pode simular solicitações entre origens diferentes e verificar se as regras de CORS bloqueiam ou permitem as solicitações conforme esperado.

Como testar a configuração de CORS usando as ferramentas de desenvolvedor do navegador

Você pode testar as configurações de CORS no nosso aplicativo de exemplo em Node.js usando as ferramentas de desenvolvedor do navegador. Siga as etapas abaixo.

Primeiro, inicie o aplicativo Node.js. Na linha de comando do diretório do aplicativo, execute o comando abaixo:

node index.js

Em seguida, acesse a página inicial do Google no navegador de sua preferência.

Página inicial do Google no modo escuro, com uma barra de pesquisa vazia, botões de pesquisa e opções de idioma, incluindo hauçá, igbo, iorubá e pidgin nigeriano

Depois, abra o console do desenvolvedor. Se você usa um navegador baseado em Chromium, use o comando Option+⌘+J (no macOS) ou Shift+CTRL+ J (no Windows/Linux). Outra opção é usar os comandos correspondentes no navegador de sua preferência.

Cole o código abaixo e pressione Return/Enter:

fetch('http://127.0.0.1:3000/books')
.then((response) => response.json())
.then((json) => console.log(json))

Com base na configuração atual de CORS do aplicativo de exemplo, provavelmente você verá erros indicando que a política de CORS bloqueou o acesso da origem (Google) ao recurso /books no servidor e que ocorreu um erro interno do servidor.

Console do Chrome DevTools mostrando uma solicitação fetch bloqueada por uma política CORS e um erro 500 Internal Server Error

O erro de acesso bloqueado ocorre porque a origem, ​​https://www.google.com, não está entre as origens autorizadas a contornar a SOP. As configurações da opção `origin` do objeto corsOptions, que passamos para o middleware cors, não permitem que essa origem receba uma resposta do servidor. Observe que, embora a visualização e a mensagem de erro possam variar de acordo com o navegador, o erro ainda indicará que a solicitação foi bloqueada.

Esse erro confirma que as regras da nossa implementação de CORS estão funcionando como esperado. O aplicativo não pode ser acessado por origens não autorizadas.

Para aprovar a solicitação de https://www.google.com, especifique essa origem na opção origin do objeto corsOptions:

const allowedOrigins = ['https://example.com', 'https://another-example.com', 'https://www.google.com'];
corsOptions.origin = (origin, callback) => {
  if (allowedOrigins.includes(origin)) {
    callback(null, true);
  } else {
    callback(new Error('Not allowed by CORS'));
  }
};

app.get('books/', cor(corsOptions), (req,res) => { /* ... */ })

Ao reiniciar o servidor e executar novamente o comando fetch no console do desenvolvedor, tudo deve funcionar como esperado. Se o erro persistir, talvez seja necessário executar o script em outro navegador. Para que o comando fetch funcione no navegador Brave, você precisará desativar o Shields. Observe que a configuração atual de CORS pode falhar no navegador Safari. Isso acontece porque o Safari aplica rigorosamente a Política de Mesma Origem (SOP), que restringe solicitações de origens diferentes.

Tela dividida mostrando a página inicial do Google e o console do Chrome DevTools exibindo uma solicitação fetch que retorna um array com três livros.

Como permitimos https://www.google.com nas configurações de CORS, recebemos uma lista de livros em vez de erros.

Próximas etapas

Embora o CORS adicione recursos úteis ao seu aplicativo, uma configuração incorreta pode expô-lo a vulnerabilidades de segurança. Diante desses riscos, siga as práticas recomendadas sempre que possível para proteger seus aplicativos Node.js contra possíveis ameaças.

Você pode configurar o middleware cors com origens, métodos HTTP, cabeçalhos e solicitações de preflight específicos para garantir uma comunicação segura entre origens diferentes. Ferramentas como Snyk Code e as ferramentas de desenvolvedor do navegador também ajudam a encontrar proativamente erros e vulnerabilidades durante o desenvolvimento. Essa abordagem ajuda a garantir que seu aplicativo esteja seguro quando entrar em produção e mantém seus sistemas e usuários protegidos.

Depois de configurar o CORS seguindo estas práticas recomendadas, experimente o Snyk Code para testar seu aplicativo em tempo real e conferir suas configurações de segurança. Você pode fazer até 200 testes por mês gratuitamente.

Comece a jogar Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.

Leia mais

Blog

Modelos de ponta encontraram as vulnerabilidades. Só o atacante encontrou as cadeias.

A análise estática encontrou as falhas, mas só os testes de ataque em aplicações ativas provaram como elas poderiam ser encadeadas para causar invasões. Uma comparação entre Evo COS, Claude Security e Claude Code Security.

feature insights context
Blog

Os ataques autônomos já chegaram. A defesa precisa acompanhar o ritmo.

Os atacantes autônomos estão reduzindo o tempo disponível para a defesa. Saiba como a descoberta, a correção, a validação e a prevenção contínuas ajudam as equipes de segurança a acompanhar esse ritmo.

Blog

Por que agentes de programação com IA continuam criando falhas de controle de acesso

Agentes de programação com IA podem gerar uma lógica de autorização que compila e passa pela revisão, mas permite que um tenant acesse os dados de outro. Saiba por que é difícil detectar falhas de controle de acesso e como evitá-las.