CORS é um daqueles assuntos que atrapalha de iniciante a veteranos.
Considerando que toda nova aplicação poderá sofrer com erros de CORS e muitas vezes leva algum tempo para resolver, ou pior, muitas vezes resolvemos da forma errada, esse post vem para ajudar nesse assunto.
Hoje abordaremos tudo que você precisa saber CORS, para economizar semanas da sua vida.
Faça bom proveito desse tempo!
Erros de CORS tomam tempo de todos, quando mais cedo você entender esse mecanismo, menos tempo vai perder olhando para o lugar errado.
É o tipo de demanda de segurança que todo mundo quer desbloquear para conseguir desenvolver o mais rápido.
O problema é que:
- A falta de entendimento faz perder tempo olhando para o lugar errado.
- As vezes o problema sequer está na sua aplicação, está na falta de configuração em um proxy reverso ou api gateway.
- O fato de ser uma implementação que só afeta browsers, faz parecer instável e confuso.
- O fato de aplicações como Curl ou Postman não sofrerem com CORS, faz tudo ficar ainda mais esquisito.
Ao mesmo tempo, se você for ao Stack Overflow, ou googlar por 5 minutos e achar um código semelhante ao abaixo e aplicar no seu projeto:
services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => builder .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader()); });
Saiba que você estará permitindo chamadas:
- de qualquer origem
- com qualquer método
- e quaisquer cabeçalhos
Se você não é um provedor de soluções em que o frontend javascript de aplicações de terceiros, desconhecidos, precisam acessar um recurso seu, você cometeu um erro grave.
É grave porque está expondo seus usuários, mas é simples de resolver porque não são necessários muitos passos, mas é necessário entender do que estamos falando.
1 A história por trás do CORS
Ao olhar desatento, erros de CORS têm comportamento instável, funcionando de um jeito e não funcionando de outro.
Estamos falando de um assunto que em menos de 5 minutos você compreende, entende e carrega para a vida toda.
Sem passar por isso, ficará batendo cabeça tentando descobrir, a cada novo projeto, como fazer funcionar.
1.1 Entendendo o fluxo
Para entendermos Cross-Origin Resource Sharing (CORS) precisamos revisitar a implementação que demandou a criação do CORS, estamos falando de Same-Origin Policy (SOP).
Same-Origin Policy é uma resposta a um tipo de ataque, e ao abordar SOP, precisamos falar desse tipo de ataque para contextualizar.
É assim que desmistificamos esse assunto.
1.2 Same-Origin Policy
Same-Origin Policy (SOP) é uma medida de segurança implementada nos browsers para proteger os dados do usuário contra acessos não autorizados.
A política se originou por volta de 1995 sendo incluída nos navegadores Netscape Navigator 2.0 e Internet Explorer 3.0.
1.2.1 Entendendo os ataques antes do Same-Origin Policy
Antes da implementação da SOP, os bowsers permitiam que scripts de uma origem acessassem livremente recursos e informações de outras origens. Isso levava a problemas de segurança, como:
- Ataques Cross-Site Scripting (XSS): Um atacante poderia injetar código malicioso em um site confiável, fazendo com que os usuários executem ações indesejadas ou divulguem informações pessoais sem o seu conhecimento.
- Ataques Cross-Site Request Forgery (CSRF): Um atacante poderia induzir um usuário a executar uma ação em outro site em seu nome, potencialmente causando danos ou comprometendo suas informações pessoais.
- Vazamento de informações: Os sites poderiam acessar informações sensíveis, como cookies e tokens de autenticação, de outros sites, levando a problemas de privacidade e segurança.
1.2.2 Exemplo
Imagine que aqui no gago.io eu implementasse propositalmente, ou inadvertidamente adicionasse uma biblioteca JS infectada.
Suponha que essa library maliciosa faça chamadas à API do Facebook, Instagram ou LinkedIn, e tentando obter dados dos serviços nos quais você está autenticado, essa chamada se passaria por você, se valendo da sua sessão de login.
Sem SOP e com uma falha de implementação de CORS seria possível obter seus dados seus e dados dos seus amigos. Ou pior, seria possível trocar sua senha, e roubar sua conta.
Mas graças ao SOP e configurações restritivas do CORS dessas aplicações, isso não é possível.
Esse tipo de ataque é real e mais comum do que se possa imaginar. Uma vez que seu sistema permite acesso irrestrito, de qualquer origem, com qualquer método, e qualquer cabeçalhos, você está abrindo mão completamente, da segurança do CORS e SOP e está deixando seu usuário exposto.
O único elo que falta para o caos é a descoberta da existência do seu sistema, e o despertar do interesse de atacantes para explorar as brechas de segurança que você deixou.
E esse momento pode chegar quando sua aplicação cresce e ganha notoriedade.
Quando ascende e ganha os holofotes.
Ou quando você investe em marketing.
Já imaginou o caos né?!
1.2.2 O que é Same-Origin Policy
A SOP foi implementada para solucionar esses problemas, restringindo o acesso a recursos e dados de diferentes origens.
Uma origem é definida por uma combinação de protocolo, domínio e porta.
Em uma chamada http://localhost:4200/api/:
- http é o protocolo/schema
- localhost é o domínio
- e 4200 é a porta.
Claro que aquela regra continua válida: HTTP tem porta default a 80 e https tem como default a 443.
Com a SOP em vigor, um script executado em um browser só pode acessar recursos e informações da mesma origem a partir da qual foi carregado.
1.3 Do SOP ao CORS
9 anos depois da implementação do Same-Origin Policy, a internet já era bem diferente. Em 2004, já começávamos a usar API’s HTTP via javascript com maior frequência.
Uma demanda natural era buscar uma forma segura, de permitir que os browsers pudessem acessar API’s de outras origens.
Daí nasce a proposta do CORS.
1.4 Cross-Origin Resource Sharing (CORS)
Cross-Origin Resource Sharing (CORS) é uma especificação que permite que recursos de uma origem sejam acessados por outra origem, contornando a Same-Origin Policy (SOP).
A necessidade de CORS surgiu com o aumento da demanda por aplicações web mais interativas e com maior compartilhamento de recursos entre diferentes origens.
Antes do CORS, os desenvolvedores enfrentavam algumas limitações significativas devido à SOP:
- Comunicação entre diferentes domínios: A SOP limitava a comunicação entre aplicações web que pertenciam a domínios diferentes. Isso restringia a colaboração e o compartilhamento de recursos entre sites e aplicações.
- Integração de APIs de terceiros: A SOP tornava difícil a integração de APIs de terceiros, pois os scripts carregados de uma origem não podiam fazer solicitações a outra origem sem contornar a política de segurança.
- Desenvolvimento de aplicações ricas em recursos: Aplicações web modernas e avançadas exigem o acesso a recursos de diferentes origens para fornecer uma experiência de usuário rica e envolvente.
Para resolver esses problemas, o W3C (World Wide Web Consortium) começou a trabalhar no CORS por volta de 2006, e a especificação foi publicada como um padrão somente em 2014.
O CORS utiliza cabeçalhos HTTP para permitir que os servidores especifiquem quais origens podem acessar seus recursos e quais tipos de solicitações são permitidas.
O CORS oferece uma maneira mais segura e flexível de compartilhar recursos entre diferentes origens. Ele permite que os desenvolvedores criem aplicações web mais robustas e integradas, ao mesmo tempo em que mantém a segurança e a privacidade do usuário em mente.
2 Mas afinal o que é CORS?
CORS é uma implementação dos browsers.
Sim, só dos browsers. Clientes C#, Java, Node, não implementam e não sofrem restrições de SOP e CORS.
CORS é implementado somente por browsers, e consiste na interceptação de chamadas com XMLHttpRequest e Fetch API, avaliando se a chamada que será realizada, vai para uma origem diferente da origem atual do browser.
Se a origem é a mesma, o request é executado, senão, um request adicional é realizado com a função de perguntar se para o servidor se o contexto atual pode ou não realizar essa chamada.
Esse request tem o nome de preflight request, e consiste em uma chamada adicional tem o VERBO OPTIONS (mesmo que sua chamada use POST, GET, PUT,) com o intuito de perguntar ao servidor:
Hey servidor diferente,
eu estou no endereço https://localhost:4200/rota/spa
quero enviar um request com o verbo POST para https://localhost:5000/api/teste,
posso?
Você até poderia implementar uma action na sua controller para receber esse request e lidar com isso manualmente, mas o ASP.NET possui configurações para que ele mesmo intercepte e lide com essa complexidade.
Ele tomará a decisão de aprovar ou recusar, com base naquele código lá do início do post.
Copiei novamente abaixo:
services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader()); });
Ou seja, esse código aqui, aceita tudo de qualquer lugar, com qualquer método, com qualquer cabeçalho, e é por isso que essa implementação é errada na maior parte do tempo.
A escolha por essa configuração se traduz em permitir que qualquer script em qualquer site ou app, em qualquer lugar, possa fazer uma chamada HTTP para sua aplicação.
Ao receber preflight request, e aceitá-lo, o browser percebe que está tudo ok e pode prosseguir com o request original. É hora de enviar o POST/GET/PUT/DELETE para esse serviço.
3 Por que essas informações são úteis?
Uma vez que você sabe o que acontece, é mais fácil corrigir.
Quantas e quantas vezes vejo jovens e até alguns veterenos, se perdendo procurando erros onde não há. Tentanto resolver o que não é para ser resolvido. Simpelsmente porque estão olhando para o lugar errado.
Se você tentar resolver CORS no seu SPA, baterá com a cara na porta.
Se tentar resolver olhando para a mesma origem, também.
E se você não tem controle sobre a origem alvo, que implementa CORS e bloqueia sua aplicação, também estará perdendo tempo, exceto caso coloque um proxy reverso ou um backend na frente.
Então, entender CORS faz compreendermos como funciona, para entender onde resolveremos.
3.1 Proxy Reverso / API Gateway
Muitas vezes o problema com CORS não é falta de configuração no ASP.NET, mas um Proxy Reverso ou API Gateway, que está configurado para aceitar somente verbos comuns como POST, GET …, tudo exceto OPTIONS.
A solução aqui é deixar passar o OPTIONS, para que sua aplicação lide com isso, ou configurar diretamente no Proxy Reverso, ou API Gateway para que ele realize essa tarefa.
3.2 ASP.NET não configurado
Também há a possibilidade da aplicação ASP.NET não ter as configurações de CORS.
Aqui basta configurar qual é a origem que pode realizar as chamadas.
builder.Services.AddCors(options => { options.AddPolicy(name: MyAllowSpecificOrigins, policy => { policy.WithOrigins("http://example.com", "http://www.contoso.com"); }); });
3.2.1 Seja restritivo
Sempre que puder, seja o mais restritivo possível. Isso garantirá que somente as chamadas que você conhece serão realizadas.
Abaixo tenho um exemplo de uma das minhas aplicações. Estamos falando de uma aplicação angular que precisa falar com um backend ASP.NET
Em desenvolvimento, temos:
SPA Angular: http://localhost:4200
API ASP.NET: https://localhost:7777/api
Entretanto, em produção temos:
SPA Angular: https://dominio
API ASP.NET: https://dominio/api
Assim, eu só preciso me preocupar com CORS no ambiente de desenvolvimento.
if (builder.Environment.IsAnyOf(Environments.Local)) { builder.Services.AddCors(options => { options.AddPolicy(name: corsPolicy, policy => { policy.WithOrigins("https://localhost:4200", "http://localhost:4200"); policy.AllowAnyMethod(); policy.WithHeaders("Content-Type", "Accept", "store", "authorization", WebExtensions.deviceIdHeader); policy.AllowCredentials(); }); }); }
3.3 Browser = Problema | Postman = Sucesso
Fiz questão de marcar ao longo do post que a implementação de CORS reside nos browsers.
Por isso, bibliotecas do backend não sofrem as restrições e validações de SOP e CORS.
POSTMAN embora possa ser encarado como um plugin do browser, não usa a stack javascript do frontend para realizar suas chamadas, e por isso não é impactado com CORS.
4 O que pode acontecer se “desabilitar” CORS?
A abordagem que mostrei no início desse post, tem a função de desabilitar CORS. Sua aplicação passa aceitar tudo, de todo lugar.
Isso quer dizer que scripts JS podem acessar suas API’s a partir de qualquer domínio, qualquer aplicação. Isso não é um problema quando sua aplicação não tem notoriedade ou não é alvo de muitos ataques.
Mas basta o sucesso para ser um dos primeiros vetores de ataque. É aqui que o risco se mostra real.
Talvez você imagine que esse tipo de ataque ocorre com uma aplicação específica, onde a obtenção de dados é feita por uma massa pequena de usuários, certo?
Não necessariamente. O segundo escalão do marketing digital, aquelas companhias que fazem propaganda em sites obscuros podem permitir que scripts sejam adicionados à campanha de marketing. E do dia para a noite você pode sofrer uma enxurrada de acessos e uma fração desses acessos serão de usuários autenticados.
Há ataques a repositórios do github, onde PR’s são enviados para bibliotecas muito comuns, com a função de permitir esse tipo de ataque. Então se você acha que não é com você, saiba que é!
5 CORS é suficiente?
SOP e CORS tem a função de limitar um tipo de ataque que acontece via browser, via javascript. Entretanto, é apenas uma fina camada de segurança.
É frágil por se tratar de limitação implementada no browser.
Burlar CORS também não é difícil, mas exige que você coloque um proxy reverso entre seu javascript e o servidor de destino, e é relativamente fácil, embora por demandar tanto, não é tão fácil de obter sucesso.
Outro ponto é que tendo um servidor no meio do caminho, todo o tráfego é centralizado a partir desse servidor, assim identificar, perceber o ataque e bloquear ou aplicar políticas de throttling, são tarefas mais fáceis.
Conclusão
Se você entender que CORS é uma implementação dos browsers, que só resolve um tipo de ataque, você já economiza tempo no throubleshooting.
Raros são os casos em que você deveria implementar CORS na mão. São aqueles casos em que estamos falando de aplicações dinâmicas em que temos de ir ao banco de dados descobrir se podemos ou não permitir.
O ASP.NET fornece uma solução elegante e fácil de configurar.
Há cenários em que CORS só é um problema no desenvolvimento, se for seu caso, só habilite CORS quando estiver trabalhando local.
Espero com esse post que você não passe perrengue sem entender o comportamento ou sem entender porque as coisas acontecem dessa forma.
0 comentários