2 pontos por GN⁺ 4 시간 전 | 1 comentários | Compartilhar no WhatsApp
  • 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-Origin para 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:19421 na 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:19421 implementar uma API REST e definir o cabeçalho Access-Control-Allow-Origin como https://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
    • 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
  • 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

 
GN⁺ 4 시간 전
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.us nã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 para localhost:19421 da 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 de localhost:19421, e como a requisição em si acontece de qualquer maneira, o backend deve ser feito de forma que não haja efeitos colaterais

    • Não sei por que este é o comentário mais votado. O OP está certo, e a explicação acima está errada
      As 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
    • Também não acho que dê para dizer que o CORS faz esse papel
      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
    • Pelo que entendi, o objetivo principal do preflight OPTIONS é bloquear requisições HTTP que normalmente não seriam permitidas, e em requisições que já são permitidas o CORS não faz nada
      Por exemplo, um POST com Content-Type igual a text/json não pode ser enviado para um host de terceiros sem preflight OPTIONS, mas um POST com multipart/form-data é permitido, e o CORS não o bloqueia. E se o endpoint não verificar rigorosamente o Content-Type e simplesmente assumir que é JSON, então qualquer site passa a conseguir enviar POST sem interação do usuário
    • “Assumindo que estamos falando apenas de métodos seguros” é uma suposição bem grande
      Um 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 com PUT/PATCH/DELETE e Content-Type que não seja de formulário acionam preflight, então o CORS é verificado antes de a requisição real chegar ao servidor
    • A parte do texto que diz “apps nativos podem gerar certificados autoassinados próprios” também é problemática
      Só 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

    • Esse documento sozinho responde a maior parte das dúvidas, não só sobre casos simples de origem, mas também sobre como o preflight funciona
  • 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

    • Já vi configuração de CORS errada até em projetos em que o mesmo desenvolvedor escreveu o frontend e o backend
      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
    • O modelo de ameaça do CORS não é tão difícil. É a situação em que um atacante leva o usuário até o próprio site e o faz executar alguma ação no seu site
    • O CORS confunde porque foi construído em cima de um modelo de permissões padrão bem estranho. multipart/form-data pode, mas JavaScript da aplicação não, por exemplo
    • Do ponto de vista do atacante e do defensor, o modelo de ameaça não é tão natural assim
      O 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
    • O CORS é ótimo para impedir que as pessoas roubem facilmente largura de banda e recursos de hospedagem. Para roubar, elas teriam de montar um proxy por conta própria, e aí fica mais fácil bloquear
  • 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

    • Talvez seja uma diferença geracional
      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
    • O CORS não é intuitivo, mas dá para entender lendo a documentação
      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

    • Acho que é uma pergunta bem boa ao contratar desenvolvedores frontend
      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

    • O erro de CORS não é uma “mensagem de erro enviada ao navegador”, e sim um erro gerado pelo navegador ao concluir que não pode permitir a requisição
      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.com obtenha a lista de inscrições de youtube.com. Mas com CORS, é possível permitir que example.com acesse youtube.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-middle

    • Na verdade, é o contrário. Quem impede esse tipo de problema de segurança é a SOP, e o CORS é o mecanismo que afrouxa a SOP para permitir interações mais complexas entre aplicações
  • Eu 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

    • A experiência de desenvolvedor com CORS e CSP é horrível. Os navegadores não dizem direito de onde o problema vem
      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.com nã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 inspetor
    • O maior problema do CORS é que a maioria dos erros parece um problema de frontend, especialmente do navegador, mas a correção real precisa ser feita no backend
    • Sinto algo parecido. As poucas vezes em que precisei lidar com CORS foi em situações do tipo “precisamos buscar algo deste servidor, mas não podemos mudar o CORS nem o CSP dele”, o que, em termos de segurança, significa “existe um sistema de segurança e precisamos contorná-lo”
      No 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 SOP
    Ainda 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