2 points par GN⁺ 4 시간 전 | 1 commentaires | Partager sur WhatsApp
  • La vulnérabilité du serveur web local de Zoom montre à quel point une frontière de sécurité peut facilement s’effondrer lorsque de nombreux développeurs web comprennent mal le fonctionnement de CORS
  • En communiquant avec le serveur local localhost:19421, Zoom transmettait les codes d’état via la taille d’image au lieu d’utiliser AJAX, ce qui s’apparente à une implémentation de contournement pour éviter CORS
  • Chrome applique aussi les en-têtes CORS aux serveurs web localhost, et la communication frontend/backend sur différents ports localhost est également prise en charge par les navigateurs
  • Une conception plus sûre consisterait à ce que le serveur local fournisse une API REST et définisse Access-Control-Allow-Origin pour limiter l’accès au seul JavaScript de zoom.us
  • Contourner la politique de même origine peut faire fonctionner le code, mais cela peut exposer les fonctions privilégiées du serveur local à tous les sites web sur Internet

Le contournement de CORS créé par le serveur web local de Zoom

  • En travaillant dans le conseil full stack auprès de développeurs de tailles d’entreprise et de secteurs variés, l’auteur a constaté de manière répétée que les développeurs web ne comprenaient pas bien CORS
  • Dans la récente vulnérabilité de Zoom, le chercheur en sécurité Jonathan Leitschuh a découvert que Zoom lançait un serveur web http://localhost:19421 sur la machine de l’utilisateur
    • Lorsqu’un utilisateur ouvre un lien Zoom, le site web de Zoom envoie une requête au serveur localhost pour lancer l’application Zoom native
    • Au lieu d’une requête AJAX classique, Zoom chargeait une image depuis le serveur web local et utilisait des tailles d’image différentes pour représenter les erreurs et codes d’état du serveur
  • L’idée selon laquelle le navigateur ignorerait la politique CORS d’un serveur localhost est fausse, et Chrome respecte les en-têtes CORS des serveurs web localhost
    • Même lorsque le frontend Create React App et l’API backend tournent sur des ports localhost différents, il s’agit bien de requêtes cross-origin, et cela est pris en charge par tous les navigateurs
  • Il semble que Zoom ait contourné CORS via cette astuce d’image après avoir vu ses requêtes AJAX bloquées
    • En conséquence, non seulement le site de Zoom, mais aussi d’autres sites web sur Internet pouvaient déclencher le comportement du client natif et accéder aux réponses

Une alternative plus sûre et les problèmes d’UX qui subsistent

  • Une implémentation sûre consisterait à faire en sorte que le serveur web sur localhost:19421 expose une API REST et définisse l’en-tête Access-Control-Allow-Origin sur https://zoom.us
    • Ainsi, seul le JavaScript exécuté depuis le domaine zoom.us pourrait communiquer avec le serveur web localhost
    • zoom.us pourrait aussi définir un en-tête Content Security Policy bloquant le rendu en iframe afin d’éviter qu’une réunion Zoom ne s’ouvre automatiquement en arrière-plan
  • Le problème selon lequel n’importe quelle page peut toujours rediriger le navigateur vers un lien de réunion zoom.us demeure
    • Cela relève toutefois davantage de l’expérience utilisateur choisie par Zoom que d’une vulnérabilité logicielle
    • Zoom rompt l’attente des utilisateurs selon laquelle cliquer sur un lien ne devrait pas ouvrir soudainement leur caméra et leur micro pour des inconnus
    • Si l’objectif est d’éviter la popup native du navigateur pour des raisons d’UX, l’application pourrait afficher sa propre popup, comme le fait efficacement Google Meet
  • Faire tourner un serveur web sur localhost est en soi une approche risquée, et il ne faut surtout pas offrir à tous les sites web d’Internet des fonctions privilégiées comme l’installation de logiciels
    • CORS existe précisément pour gérer ce type de situation en sécurité, et il ne faut donc pas le contourner

La confusion autour de CORS ne concerne pas seulement Zoom

  • Il n’est pas certain que Zoom ait choisi cette approche parce que l’entreprise ne comprenait réellement pas CORS
    • lerunicorn sur Reddit estime que Firefox peut bloquer les XHR depuis une origine sécurisée vers une origine non sécurisée
    • Mais Firefox le prend en charge lorsque l’origine est localhost
    • Une application native peut générer son propre certificat autosigné, et il est aussi possible d’utiliser une extension de navigateur
    • Dans tous les cas, cela ne justifie en rien l’absence de filtrage par origine
  • La confusion autour de CORS n’est pas propre à Zoom
  • Les développeurs veulent faire marcher leur code, mais contourner entièrement la politique de même origine expose, comme dans le cas de Zoom, des privilèges locaux à des sites web externes
  • La confusion autour de CORS touche aussi bien les développeurs expérimentés que les débutants, et il n’est pas clair si l’API CORS est excessivement complexe ou si la formation sur CORS et CSP est insuffisante, mais l’approche actuelle ne fonctionne manifestement pas bien

1 commentaires

 
GN⁺ 4 시간 전
Commentaires Hacker News
  • Il semble que le TFA non plus n’ait pas vraiment compris CORS, ou l’explique très mal
    Access-Control-Allow-Origin: https://zoom.us ne garantit pas que seul le JavaScript du domaine zoom.us puisse communiquer avec le serveur localhost. Le JavaScript d’autres sites web peut tout autant envoyer des requêtes vers localhost:19421. CORS ne sert pas à restreindre quelque chose, mais à assouplir une restriction par défaut. Cet en-tête permet seulement au JavaScript exécuté sur zoom.us de lire la réponse de localhost:19421, mais la requête elle-même a lieu dans tous les cas, donc le backend doit être conçu pour éviter tout effet de bord

    • Je ne comprends pas pourquoi c’est le commentaire le plus voté. L’OP a raison, et l’explication ci-dessus est fausse
      Les requêtes GET sont bien envoyées, mais elles sont censées être idempotentes à l’origine, donc si le serveur est correctement implémenté, elles ne peuvent pas produire d’effets de bord, et pour GET, l’enjeu principal est de savoir si la réponse peut être lue. À l’inverse, pour les requêtes non idempotentes pouvant avoir des effets de bord, une requête preflight OPTIONS est d’abord envoyée à la place de la vraie requête en contexte cross-origin, et si la réponse OPTIONS ne contient pas les bons en-têtes, la requête réelle n’est pas envoyée
    • Je ne pense pas non plus qu’on puisse dire que CORS remplit ce rôle
      Les malentendus autour de CORS sont tellement répandus, et la documentation est souvent contradictoire, qu’il est difficile de s’attendre à ce qu’un interlocuteur inconnu l’ait correctement implémenté. Quand un protocole provoque une confusion aussi généralisée, même si une partie fonctionne correctement, on ne peut pas savoir si l’autre fera de même. Si les gens ont corrigé leur code jusqu’à ce qu’il marche avec d’autres implémentations, il devient flou de savoir si l’erreur venait de chez eux ou de l’autre côté
    • D’après ce que j’ai compris, le but principal du preflight OPTIONS est d’empêcher les requêtes HTTP qui ne sont normalement pas autorisées, et CORS ne fait rien pour les requêtes déjà autorisées à l’origine
      Par exemple, un POST avec Content-Type à text/json ne peut pas être envoyé vers un hôte tiers sans preflight OPTIONS, alors qu’un POST en multipart/form-data est autorisé et n’est pas bloqué par CORS. Et si l’endpoint ne vérifie pas strictement le Content-Type et suppose qu’il s’agit de JSON, alors n’importe quel site web peut envoyer un POST sans action de l’utilisateur
    • « Supposons qu’on ne parle que de méthodes sûres » est une hypothèse assez forte
      Un bon développeur web ne devrait pas faire en sorte que GET/HEAD/OPTIONS modifient l’état, et rejoindre une réunion est bien un changement d’état. PUT/DELETE doivent aussi être idempotentes. Une API POST qui n’utilise ni JSON ni formulaire doit vérifier l’en-tête Content-Type, et les PUT/PATCH/DELETE ainsi que les POST avec un Content-Type autre qu’un format de formulaire déclenchent un preflight, ce qui permet à CORS d’être vérifié avant que la requête réelle n’atteigne le serveur
    • Le passage de l’article disant que « les applications natives peuvent générer leur propre certificat auto-signé » pose aussi problème
      Le simple fait de créer un certificat ne suffit pas : il doit être installé comme certificat de CA racine dans tous les magasins de confiance des navigateurs de la machine. Si la clé privée de la CA racine n’est pas correctement protégée, n’importe quel site web peut effectuer une attaque de l’homme du milieu, donc il faut au minimum une contrainte de nom (https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10). Or Chrome ne faisait pas fonctionner cela avec une CA racine avant la v112 en 2023 (https://alexsci.com/blog/name-non-constraint/), il fallait donc ajouter une CA intermédiaire et y appliquer la restriction. Bien sûr, la bonne pratique reste de jeter la clé de la CA racine
      Sur un ancien projet utilisant une CA racine locale, j’avais ajouté des contraintes de base, mais je les avais mises par erreur sur la CA racine et je n’avais pas testé sur tous les navigateurs
  • J’aimerais que davantage de gens lisent la documentation MDN sur CORS. Elle m’a beaucoup aidé à comprendre CORS, et en lisant les commentaires ici, je ne pensais pas que tant de gens avaient autant de mal avec le sujet
    https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS

    • Ce seul document répond à l’essentiel, non seulement pour les cas simples d’origine, mais aussi pour le fonctionnement du preflight
  • Ce n’est pas seulement CORS qui est difficile à comprendre : beaucoup de développeurs comprennent mal le modèle de menace
    Même avec une explication, il est souvent difficile de percevoir pourquoi c’est un gros problème. En particulier, ce sont souvent les développeurs backend qui configurent CORS, mais comme CORS n’est pas un mécanisme de protection des autorisations d’accès, cela ne paraît pas très important côté backend. Les attaquants ont l’impression de ne rien pouvoir voler, tandis que côté frontend, cela ressemble facilement à un obstacle pénible. Cet article montre bien des exemples concrets

    • J’ai déjà vu une configuration CORS incorrecte dans un projet où le même développeur écrivait à la fois le frontend et le backend
      En tant qu’exploitant, je l’ai corrigée correctement au niveau du load balancer, et au moins l’application fonctionne désormais. CORS est difficile à comprendre, mais il est encore plus regrettable de voir combien de développeurs ne comprennent pas le modèle de menace que CORS essaie de contrer, ni le développement web en général, en particulier le protocole HTTP
    • Le modèle de menace de CORS n’est pas si difficile. C’est la situation où un attaquant attire un utilisateur vers son site et le pousse à effectuer une action sur votre site
    • CORS est déroutant parce qu’il repose sur un modèle d’autorisations par défaut assez étrange. multipart/form-data est accepté, mais pas le JavaScript de l’application, par exemple
    • Vu sous l’angle de l’attaquant et du défenseur, le modèle de menace n’a rien de très intuitif
      CORS est optionnel, et d’autres bibliothèques ou outils peuvent tout simplement l’ignorer. En pratique, CORS n’a de sens que pour empêcher les XSS et CSRF visant un utilisateur humain réellement connecté ; pour les autres scénarios d’attaque, cela ne sert à rien, car on utilise de toute façon des scripts ou programmes capables de falsifier les en-têtes HTTP. C’est pour cela que les gens finissent souvent par activer toutes les options CORS, ce qui est le pire cas possible puisqu’on autorise alors XSS et CSRF
    • CORS est excellent pour empêcher les gens de voler facilement de la bande passante et des ressources d’hébergement. Pour le faire, ils doivent monter leur propre proxy, ce qui les rend plus faciles à bloquer
  • Cette section de commentaires semble vraiment d’un niveau d’information assez faible, et elle démontre plutôt exactement le propos de l’auteur

    • C’est peut-être une question de génération
      Si on faisait du développement web avant l’apparition de CORS, on sait qu’à l’origine les requêtes inter-domaines étaient interdites, et que CORS est apparu pour contourner cette sécurité. Il est donc facile d’intégrer l’idée qu’il suffit d’activer CORS pour faire ce qu’on veut
      À l’inverse, quelqu’un qui a appris le développement web après CORS essaie une requête cross-origin, voit que le navigateur décide qu’elle n’est pas autorisée, tente un preflight CORS, puis, si ça échoue, voit une erreur CORS dans la console. Si on ne connaît pas le fonctionnement interne et qu’on n’a pas lu la documentation, on peut en déduire que CORS est la cause qui bloque la requête et chercher à « désactiver CORS ». Mais CORS n’est pas la cause du problème, c’est la solution
      Comme des gens ayant la même confusion répètent cela avec assurance dans les tutoriels et les discussions en ligne, ça devient encore plus déroutant
    • CORS n’est pas intuitif, mais on peut le comprendre en lisant la documentation
      https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
  • En lisant les commentaires, j’ai constaté que je n’étais pas le seul. Si personne ne comprend CORS, c’est parce que c’est beaucoup trop complexe et plein de conflits
    Les standards et les en-têtes changent sans arrêt, donc les développeurs bricolent généralement jusqu’à ce que ça marche, déploient le produit, et s’arrêtent là. Même quand ça fonctionne, il peut rester des erreurs et des avertissements dans la console développeur, mais si tout semble bien marcher en apparence, on évite simplement d’y toucher

  • Pour comprendre CORS, il faut d’abord comprendre la policy de même origine
    Surtout si la question « pourquoi est-ce nécessaire ? » vous paraît difficile, mieux vaut commencer ici : https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy
    J’ai déjà utilisé la policy de même origine comme question d’entretien, mais beaucoup de candidats ne la connaissaient pas, donc cette question apportait peu d’informations utiles

    • Je pense que c’est une assez bonne question quand on recrute un développeur frontend
      Si quelqu’un a développé des applications web, il a forcément dû rencontrer un jour la policy de même origine. S’il ne la connaît pas, ça amène généralement à poser plus de questions sur la façon dont il communiquait avec le backend, par exemple. Selon le poste, savoir s’il a rencontré un problème CORS mais s’est contenté d’appliquer le contournement le plus rapide avant de l’oublier, ou s’il a réellement essayé de le comprendre, peut être un signal utile
      C’est moins adapté à un poste backend, car tous les développeurs backend n’ont pas travaillé de près avec une équipe frontend confrontée fréquemment à des problèmes CORS
  • Ce dont je me souviens à propos de CORS, c’est que le débogage prend bien plus de temps que prévu, que les messages d’erreur du navigateur sont volontairement pauvres, et qu’au début il est difficile de distinguer une erreur CORS d’autres modes d’échec

    • Une erreur CORS n’est pas un « message d’erreur envoyé au navigateur », mais une erreur générée parce que le navigateur a jugé qu’il ne pouvait pas autoriser la requête
      Bien sûr, si le serveur ne comprend pas les requêtes CORS et renvoie une réponse étrange, cela peut au final se traduire par un échec CORS
  • En voyant cette section de commentaires, je trouve ça assez amusant, alors j’ajoute ceci : la policy de même origine protège contre l’exfiltration d’informations vers des sites web auxquels le navigateur n’a pas donné accès, et CORS permet d’affaiblir cette protection
    Par exemple, la policy de même origine empêche example.com de récupérer la liste des abonnements de youtube.com. Mais avec CORS, on peut autoriser example.com à accéder à youtube.com/public/*
    Un autre usage est d’empêcher qu’une API backend fonctionne sous un autre frontend et mène à un vol de données. Par exemple, cela évite une situation où l’utilisateur est bien connecté au vrai service, mais se trouve sur g00gle.com, et où toutes les requêtes pourraient faire l’objet d’une attaque de l’homme du milieu

    • Plus exactement, c’est l’inverse. C’est la SOP qui empêche ce type de problème de sécurité, et CORS est le mécanisme qui assouplit la SOP pour permettre des interactions plus complexes entre applications
  • J’en fais aussi partie. CORS est un sujet qu’il faut réétudier périodiquement, et je l’oublie toujours, donc ça ne reste pas bien en tête
    C’est sans doute parce que je suis développeur backend et que je rencontre rarement des problèmes CORS. J’ai tendance à oublier ce que je n’utilise pas tous les jours

    • L’expérience développeur avec CORS et CSP est atroce, parce que les navigateurs n’indiquent pas correctement d’où vient le problème
      Dans un monde normal, les messages d’erreur donneraient des indices comme « en-tête de réponse » ou « balise meta », mais on dirait que les grands éditeurs de navigateurs embauchent des gens spécialisés dans les messages énigmatiques. Le « requested resource » de Chrome est un peu mieux, mais reste cryptique
      Un meilleur message dirait par exemple que la ressource https://bank.com n’autorise pas les requêtes cross-origin parce qu’elle n’a pas d’en-tête CORS, ou que l’origine actuelle ne figure pas dans la liste autorisée par CORS. Il devrait aussi montrer la requête preflight dans l’onglet réseau, avec un lien vers MDN. Pour CSP aussi, il vaudrait mieux indiquer que cette page ne peut pas récupérer la ressource à cause de l’en-tête CSP de la page, et faire le lien vers les en-têtes de requête de la page ou la balise meta dans l’inspecteur
    • Le plus gros problème avec CORS, c’est que la plupart des erreurs ressemblent à des problèmes frontend, en particulier à des problèmes de navigateur, alors que la correction doit en réalité se faire côté backend
    • J’ai eu une impression semblable. Les quelques fois où j’ai dû gérer CORS, c’était dans des situations du type « il faut récupérer quelque chose depuis ce serveur, mais on ne peut pas modifier son CORS ou sa CSP », ce qui, en termes de sécurité, revient à dire : « il y a un système de sécurité et il faut le contourner »
      Au final, cela repose généralement sur l’hypothèse que le serveur n’est accessible qu’à des requêtes navigateur non modifiées. La vulnérabilité de Zoom existait parce qu’il était trop facile de contourner CORS et CSP côté client, et même si Zoom a effectivement été mauvais, paresseux et stupide, j’ai l’impression que la communauté qui continue à maintenir ce modèle porte aussi une part de responsabilité
  • Je comprends comment la policy de même origine empêche le navigateur d’exécuter des scripts malveillants pour exfiltrer des informations. Je comprends aussi qu’avec l’en-tête Access-Control-Allow-Origin, le serveur déclare faire confiance à des origines supplémentaires et assouplit ainsi la SOP
    En revanche, je ne comprends toujours pas à quoi sert l’en-tête Access-Control-Allow-Headers. Il ne semble pas améliorer la sécurité du navigateur, et encore moins celle du serveur. Je me demande si les concepteurs du protocole l’ont ajouté « pour faire complet ». À ce sujet : https://stackoverflow.com/questions/17992042