Desenvolvedores não entendem CORS (2019)
(fosterelli.co)- A vulnerabilidade do servidor web local do Zoom mostrou como a fronteira de segurança pode ruir facilmente quando muitos desenvolvedores web entendem mal como o CORS funciona
- Ao se comunicar com o servidor local em
localhost:19421, o Zoom transmitia códigos de status pelo tamanho da imagem em vez de usar AJAX, o que pode ser interpretado como uma implementação de contorno para evitar o CORS - O Chrome aplica cabeçalhos CORS também a servidores web em localhost, e a comunicação entre frontend e backend em diferentes portas de localhost também é suportada pelo navegador
- Um design mais seguro seria o servidor local fornecer uma API REST e definir
Access-Control-Allow-Originpara restringir o acesso apenas ao JavaScript do zoom.us - Contornar a política de mesma origem pode fazer o código funcionar, mas também pode expor a todos os sites da internet funções privilegiadas do servidor local
O contorno de CORS criado pelo servidor web local do Zoom
- Ao trabalhar com consultoria full-stack e lidar com desenvolvedores de vários tamanhos de empresa e setores, ficou claro repetidamente que muitos desenvolvedores web não entendem CORS
- Na vulnerabilidade recente do Zoom, o pesquisador de segurança Jonathan Leitschuh descobriu que o Zoom iniciava um servidor web em
http://localhost:19421na máquina do usuário- Quando o usuário abre um link do Zoom, o site do Zoom envia uma requisição ao servidor localhost para iniciar o aplicativo nativo do Zoom
- Em vez de uma requisição AJAX comum, ele carregava uma imagem do servidor local do Zoom e representava erros e códigos de status do servidor com tamanhos diferentes dessa imagem
- A interpretação de que o navegador ignora a política de CORS para servidores localhost está errada, e o Chrome respeita os cabeçalhos CORS de servidores web em localhost
- Quando um frontend Create React App e uma API backend rodam em portas diferentes de localhost, também ocorre uma requisição cross-origin, e isso é suportado em todos os navegadores
- Parece que o Zoom contornou o CORS com esse hack de imagem depois que as requisições AJAX foram bloqueadas
- Como resultado, não só o site do Zoom, mas também outros sites da internet puderam acionar o comportamento do cliente nativo e acessar a resposta
Alternativa segura e o problema de UX que permanece
- Uma implementação segura seria o servidor web em
localhost:19421implementar uma API REST e definir o cabeçalhoAccess-Control-Allow-Origincomohttps://zoom.us- Assim, apenas JavaScript executado no domínio zoom.us poderia se comunicar com o servidor web local
- O zoom.us também poderia usar um cabeçalho Content Security Policy para bloquear renderização em iframe e impedir que reuniões do Zoom sejam abertas automaticamente em segundo plano
- O problema de qualquer página ainda poder redirecionar o navegador para um link de reunião do zoom.us continua existindo
- Ainda assim, isso se aproxima mais de uma experiência do usuário escolhida pelo Zoom do que de uma vulnerabilidade de software
- O Zoom quebra a expectativa do usuário de que, ao clicar em um link, câmera e microfone não serão abertos de repente para pessoas desconhecidas
- Se quiser evitar o popup padrão do navegador por razões de UX, também seria possível mostrar um popup dentro do app, e o Google Meet usa bem esse tipo de abordagem
- Executar um servidor web em localhost já é, por si só, uma tentativa arriscada, e especialmente funções privilegiadas como instalação de software não deveriam ser oferecidas a todos os sites da internet
- O CORS existe justamente para lidar com esse tipo de situação com segurança, portanto não deve ser contornado
Confusão sobre CORS não é erro só do Zoom
- Não está claro se o Zoom realmente escolheu esse caminho por não entender CORS
- O usuário lerunicorn no Reddit argumenta que o Firefox pode bloquear XHR de origens seguras para origens inseguras
- Mas o Firefox suporta isso quando a origin é localhost
- Aplicativos nativos podem gerar seu próprio certificado autoassinado e também podem usar extensões de navegador
- Em nenhum caso isso justifica omitir a filtragem por origem
- A confusão com CORS não é um problema exclusivo do Zoom
- Há muitas perguntas no Stack Overflow sobre
Access-Control-Allow-Origin - Entre os exemplos de Express, há páginas que recomendam padrões inseguros que, se copiados diretamente, podem tornar a aplicação vulnerável
- Outros fornecedores também já sofreram a mesma vulnerabilidade que o Zoom
- Há muitas perguntas no Stack Overflow sobre
- Desenvolvedores querem fazer o código funcionar, mas contornar por completo a política de mesma origem expõe privilégios locais a sites externos, como no caso do Zoom
- A confusão com CORS aparece tanto entre desenvolvedores experientes quanto iniciantes, e embora não esteja claro se a API de CORS é complexa demais ou se falta educação sobre CORS e CSP, a forma atual não está funcionando bem
1 comentários
Comentários do Hacker News
Parece que a TFA também não entendeu direito CORS ou explicou de forma muito errada
Access-Control-Allow-Origin: https://zoom.usnão garante que apenas o JavaScript do domínio zoom.us possa se comunicar com o servidor localhost. O JavaScript de outros sites também pode enviar requisições paralocalhost:19421da mesma forma. CORS não é um mecanismo para restringir algo, e sim para relaxar uma restrição padrão. Esse cabeçalho apenas permite que o JavaScript executado em zoom.us leia a resposta delocalhost:19421, e como a requisição em si acontece de qualquer maneira, o backend deve ser feito de forma que não haja efeitos colateraisAs requisições GET são enviadas, mas em princípio devem ser idempotentes, então, se o servidor foi implementado corretamente, não podem causar efeitos colaterais; no caso de GET, o ponto central é se a resposta pode ser lida ou não. Já as requisições não idempotentes, que podem ter efeitos colaterais, em contexto cross-origin recebem antes uma requisição preflight OPTIONS em vez da requisição real, e se a resposta ao OPTIONS não tiver os cabeçalhos corretos, a requisição real não é enviada
Os mal-entendidos sobre CORS são disseminados demais, e a documentação muitas vezes até se contradiz, então é difícil esperar que um desconhecido tenha implementado isso corretamente. Quando um protocolo gera esse nível de confusão em toda parte, mesmo que um lado esteja funcionando corretamente, não dá para saber se o outro também está. Se as pessoas foram ajustando o código até funcionar com outras implementações, também fica nebuloso saber se o erro estava do próprio lado ou do outro
Por exemplo, um POST com
Content-Typeigual atext/jsonnão pode ser enviado para um host de terceiros sem preflight OPTIONS, mas um POST commultipart/form-dataé permitido, e o CORS não o bloqueia. E se o endpoint não verificar rigorosamente oContent-Typee simplesmente assumir que é JSON, então qualquer site passa a conseguir enviar POST sem interação do usuárioUm bom desenvolvedor web não deve fazer GET/HEAD/OPTIONS mudarem estado, e entrar em uma reunião é uma mudança de estado. PUT/DELETE também devem ser idempotentes. APIs POST que não usam JSON nem formulário devem verificar o cabeçalho
Content-Type, e POST comPUT/PATCH/DELETEeContent-Typeque não seja de formulário acionam preflight, então o CORS é verificado antes de a requisição real chegar ao servidorSó criar o certificado não faz isso funcionar; ele precisa ser instalado como certificado de CA raiz em todos os armazenamentos de confiança de navegadores da máquina. Se a chave privada da CA raiz não for protegida adequadamente, qualquer site pode fazer um ataque man-in-the-middle, então no mínimo é necessário haver restrições de nome (https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10). Só que no Chrome isso não funcionava a partir de uma CA raiz até a v112 em 2023 (https://alexsci.com/blog/name-non-constraint/), então era preciso adicionar uma CA intermediária e aplicar a restrição nela. Claro, o certo é descartar a chave da CA raiz
Antes, em um projeto que usava uma CA raiz local, cheguei a adicionar constraints básicas, mas coloquei isso errado na CA raiz e nem testei em todos os navegadores
Queria que mais gente lesse a documentação de CORS da MDN. Ela me ajudou bastante quando tentei entender CORS, e vendo os comentários aqui eu não fazia ideia de que as pessoas tinham tanta dificuldade assim
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
O difícil de entender não é só o CORS; muitos desenvolvedores também não entendem direito o modelo de ameaça
Mesmo ouvindo a explicação, muitas vezes não fica claro por que isso é um grande problema. Especialmente porque quem costuma configurar CORS é o backend, e como CORS não é um mecanismo de proteção de autorização, do ponto de vista do backend ele não parece tão importante. Para o atacante, parece que ele não consegue levar nada, e do ponto de vista do frontend pode parecer só um obstáculo irritante. Este texto mostra bem exemplos concretos
Como responsável por operações, corrigi isso de novo no load balancer, e pelo menos a aplicação agora funciona. CORS é difícil de entender, mas mais triste ainda é ver que muitos desenvolvedores não entendem não só o modelo de ameaça que o CORS tenta mitigar, como também o desenvolvimento web em geral, especialmente o protocolo HTTP
multipart/form-datapode, mas JavaScript da aplicação não, por exemploO CORS é opcional, e outras bibliotecas ou ferramentas podem simplesmente ignorá-lo. Na prática, CORS só faz sentido para impedir XSS e CSRF contra um usuário humano realmente logado; em outros cenários de ataque ele é inútil, porque de qualquer forma usariam scripts ou programas capazes de forjar cabeçalhos HTTP. Por isso as pessoas acabam ativando todas as opções de CORS, o que é o pior caso possível, pois permite XSS e CSRF
Esta seção de comentários realmente parece ter um nível de informação muito baixo e, na verdade, acaba provando exatamente o ponto do autor
Se você fazia desenvolvimento web antes de o CORS existir, entende que requisições entre domínios eram originalmente proibidas e que o CORS surgiu para contornar essa restrição de segurança. Por isso, é fácil aceitar a ideia de que, para fazer o que você quer, basta ativar o CORS
Já quem aprendeu desenvolvimento web depois do CORS só vê o fluxo de tentar uma requisição cross-origin, o navegador decidir que ela não é permitida, tentar o preflight de CORS e, se falhar, mostrar um erro de CORS no console. Se a pessoa não conhece o funcionamento interno e tenta adivinhar sem ler a documentação, acaba achando que o CORS é a causa do bloqueio e tenta “desativar o CORS”. Mas o CORS não é a causa do problema, e sim a solução
Como outras pessoas com o mesmo mal-entendido repetem isso com confiança em tutoriais e discussões online, tudo fica ainda mais confuso
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
Lendo os comentários, confirmei que não sou só eu. Ninguém entende CORS porque é complexo demais e cheio de conflitos
Os padrões e os headers também vivem mudando, então os desenvolvedores geralmente vão mexendo em várias coisas até funcionar, publicam o produto e encerram o assunto. Mesmo funcionando, podem continuar aparecendo erros e avisos no console do desenvolvedor, mas se por fora parece estar tudo certo, ninguém mais quer mexer
Para entender CORS, primeiro é preciso entender a same-origin policy
Principalmente se a parte do “por que isso é necessário?” for difícil, vale começar por aqui: https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy
Já usei a same-origin policy como pergunta de entrevista, mas como muitos candidatos não estavam familiarizados com isso, a pergunta acabava rendendo pouca informação
Se a pessoa desenvolveu aplicações web, em algum momento deveria ter esbarrado na same-origin policy. Se não conhece, normalmente isso leva a perguntar mais sobre como ela se comunicava com o backend, por exemplo. Em alguns cargos, também é um sinal útil saber se ela já encontrou problemas de CORS, aplicou só o atalho mais rápido e esqueceu, ou se de fato tentou entender
Para funções de backend, é menos adequado. Nem todo desenvolvedor backend trabalhou de perto com equipes frontend que lidam com problemas de CORS com frequência
O que lembro sobre CORS é que o debugging leva muito mais tempo do que o esperado, as mensagens de erro do navegador são intencionalmente pobres e, no começo, é difícil distinguir erro de CORS de outros modos de falha
Claro, se o servidor não entende a requisição CORS e devolve uma resposta estranha, isso pode acabar sendo traduzido como falha de CORS
Já que a seção de comentários está bem divertida, acrescentando: a same-origin policy protege contra o navegador vazar informações para sites sem permissão de acesso, e o CORS permite enfraquecer essa proteção
Por exemplo, a same-origin policy impede que
example.comobtenha a lista de inscrições deyoutube.com. Mas com CORS, é possível permitir queexample.comacesseyoutube.com/public/*Outro uso é impedir que uma API de backend funcione sob outro frontend e acabe facilitando roubo de dados. Por exemplo, evita uma situação em que o usuário está logado no serviço real, mas está em
g00gle.com, e todas as requisições podem sofrer man-in-the-middleEu também sou uma dessas pessoas. CORS é um tema que preciso reaprender periodicamente, e sempre esqueço, então nunca fica bem fixado na cabeça
Acho que é porque sou desenvolvedor backend e quase nunca esbarro em problemas de CORS. Costumo esquecer bem rápido o que não uso no dia a dia
Num mundo normal, a mensagem de erro traria pistas como “header de resposta” ou “meta tag”, mas parece que os grandes fabricantes de navegadores contrataram gente especializada em escrever mensagens enigmáticas. O “requested resource” do Chrome é um pouco melhor, mas ainda parece um código secreto
Uma mensagem melhor diria, por exemplo, que o recurso de
https://bank.comnão permite requisições cross-origin por não ter header de CORS, ou que a origem atual não está na lista de permissões de CORS. Também deveria mostrar a requisição preflight na aba de rede e um link para o MDN. Com CSP, também seria melhor dizer que não foi possível carregar o recurso por causa do header de CSP desta página, com links para o header da requisição da página ou para a meta tag no inspetorNo fim, quase sempre isso depende da suposição de que o servidor só será acessado por requisições de navegador não adulteradas. A vulnerabilidade do Zoom aconteceu porque era fácil demais contornar CORS e CSP no lado do cliente, e embora o Zoom realmente tenha sido ruim, preguiçoso e tolo, sinto que a comunidade que continua sustentando esse modelo também tem parte da culpa
Entendo como a same-origin policy impede que o navegador execute scripts maliciosos e vaze informações. Também entendo que, com o header
Access-Control-Allow-Origin, o servidor declara confiar em origens adicionais e relaxa a SOPAinda assim, ainda não entendo o propósito do header
Access-Control-Allow-Headers. Não parece melhorar a segurança do navegador, e menos ainda a do servidor. Fico me perguntando se os projetistas do protocolo colocaram isso “por completude”. Relacionado: https://stackoverflow.com/questions/17992042