O Problema que o CORS Resolve: Same-Origin Policy

Antes de falar de headers, eu preciso falar de origin. Origin é a combinação de esquema + host + porta. Para o navegador, https://app.com e http://app.com não são a mesma coisa. https://app.com e https://api.app.com também não. A tabela abaixo resume os casos mais comuns:

CORS não é detalhe: por que times ainda erram esse básico

| Origin | Descrição |
| --- | --- |
| https://app.com | Uma API de um site |
| https://api.app.com | Uma API de um site, em um subdomínio |
| https://app.com:8080 | Uma API em um subdomínio e porta |

A partir dessa definição entra a Same-Origin Policy, ou SOP. Essa política do browser impede que um script carregado em uma origin leia livremente respostas vindas de outra. O ponto central aqui é o verbo ler. O navegador pode até permitir que alguns requests cross-origin sejam disparados, mas ele não entrega a resposta ao JavaScript da página se a política não for satisfeita.

Sem SOP, um site malicioso aberto pela vítima poderia enviar um fetch para o webmail dela, para o internet banking ou para o painel administrativo em que ela já está autenticada, reaproveitando cookies enviados automaticamente pelo navegador. Se a resposta voltasse livremente para o JavaScript do atacante, bastaria parsear o JSON ou o HTML e exfiltrar tudo. A SOP existe justamente para quebrar esse caminho.

E onde entra o CORS? CORS é a exceção controlada a essa regra. O servidor de destino informa, por headers HTTP, quais origins podem ler sua resposta. O browser recebe a resposta, verifica esses headers e decide se libera ou não o conteúdo para o JavaScript da página.

Esse detalhe explica uma confusão que eu vejo toda semana: curl, Postman, Insomnia e chamadas backend-para-backend não têm problema de CORS porque não existe navegador aplicando SOP. Se o endpoint responde 200 no Postman e falha no browser, o problema não é “a API está quebrada”. O problema é que o navegador está exigindo uma política explícita para aquela leitura cross-origin.

Anatomia de uma Requisição CORS: Simples vs Preflight

Nem toda requisição cross-origin requer um preflight. Algumas requisições são simples, ou seja, o navegador não envia um preflight OPTIONS antes de disparar a requisição. Por exemplo, uma requisição GET para uma imagem ou uma requisição POST para um formulário. Essas requisições são simples porque o navegador não tem dúvidas de que a leitura cross-origin é segura.

Outras requisições, como uma requisição POST para uma API, exigem um preflight OPTIONS. O navegador envia um preflight OPTIONS para garantir que a leitura cross-origin é segura. Se o preflight OPTIONS falhar, a requisição cross-origin falha também. Se o preflight OPTIONS for bem-sucedido, o navegador envia a requisição cross-origin normalmente.

Erros Comuns e Como Corrigi-los

Existem alguns erros comuns que eu vejo em pentests e code reviews. Um deles é o uso de Access-Control-Allow-Origin: em vez de um valor específico. Isso é um erro porque significa que qualquer origin pode ler a resposta. Isso é um risco porque pode permitir que um atacante leia respostas sensíveis.

Outro erro comum é não ter o header Access-Control-Allow-Methods. Isso permite que o atacante mude o método da requisição, como de GET para POST, para obter dados não permitidos.

Configurando CORS em NGINX e ASP.NET Core

Para configurar CORS em NGINX, você pode usar o middleware CORS. Para ASP.NET Core, você pode usar o middleware CORS também. Ambos os módulos fornecem configurações para permitir ou negar CORS para diferentes origins.

Conclusão

CORS é uma ferramenta importante para proteger APIs contra ataques cross-origin. É importante entender como ele funciona e como configurá-lo de forma segura. Se você configurar CORS de forma errada, você pode acabar exposto a ataques que poderiam ser evitados. Compreender CORS é essencial para desenvolvedores web que querem proteger suas APIs de ataques maliciosos.

AindaCorsDetalheErramTimes