2 Punkte von GN⁺ 4 시간 전 | 1 Kommentare | Auf WhatsApp teilen
  • Die Schwachstelle im lokalen Webserver von Zoom zeigte, wie leicht Sicherheitsgrenzen zusammenbrechen können, wenn viele Webentwickler die Funktionsweise von CORS missverstehen
  • Bei der Kommunikation mit dem lokalen Server localhost:19421 übermittelte Zoom Statuscodes über Bildgrößen statt per AJAX; das lässt sich als Umgehung von CORS interpretieren
  • Chrome wendet CORS-Header auch auf localhost-Webserver an, und die Kommunikation zwischen Frontend und Backend auf unterschiedlichen localhost-Ports wird im Browser ebenfalls unterstützt
  • Ein sichereres Design wäre, wenn der lokale Server eine REST API bereitstellt und Access-Control-Allow-Origin so setzt, dass nur JavaScript von zoom.us darauf zugreifen kann
  • Wer die Same-Origin-Policy umgeht, kann den Code zwar zum Laufen bringen, legt damit aber privilegierte Funktionen des lokalen Servers für alle Websites im Internet offen

Die CORS-Umgehung durch den lokalen Zoom-Webserver

  • Bei der Full-Stack-Beratung in Unternehmen unterschiedlichster Größen und Branchen zeigte sich immer wieder, dass Webentwickler CORS nicht richtig verstehen
  • In der jüngsten Zoom-Schwachstelle entdeckte der Sicherheitsforscher Jonathan Leitschuh, dass Zoom auf dem Rechner des Nutzers einen Webserver unter http://localhost:19421 startet
    • Öffnet ein Nutzer einen Zoom-Link, sendet die Zoom-Website eine Anfrage an den localhost-Webserver, um die native Zoom-App zu starten
    • Statt gewöhnlicher AJAX-Anfragen lud der lokale Zoom-Webserver ein Bild und kodierte Fehler- bzw. Statuscodes über unterschiedliche Bildgrößen
  • Die Annahme, Browser würden die CORS-Richtlinie von localhost-Servern ignorieren, ist falsch; Chrome beachtet CORS-Header auch bei localhost-Webservern
    • Auch wenn ein Create-React-App-Frontend und eine Backend-API auf unterschiedlichen localhost-Ports laufen, entstehen Cross-Origin-Requests, und das wird von allen Browsern unterstützt
  • Nachdem AJAX-Anfragen blockiert wurden, scheint Zoom CORS mit einem Bild-Hack umgangen zu haben
    • Dadurch konnten nicht nur die Zoom-Website, sondern auch andere Websites im Internet Aktionen des nativen Clients auslösen und auf Antworten zugreifen

Sichere Alternative und verbleibende UX-Probleme

  • Eine sichere Umsetzung wäre, wenn der Webserver auf localhost:19421 eine REST API implementiert und den Header Access-Control-Allow-Origin auf https://zoom.us setzt
    • Dann könnte nur JavaScript, das auf der Domain zoom.us ausgeführt wird, mit dem localhost-Webserver kommunizieren
    • zoom.us könnte zusätzlich einen Content-Security-Policy-Header setzen, der das Rendern in iframes blockiert, damit Zoom-Meetings nicht im Hintergrund automatisch geöffnet werden
  • Das Problem, dass jede Seite den Browser weiterhin auf einen zoom.us-Meeting-Link umleiten kann, bleibt bestehen
    • Das ist allerdings eher eine Frage der von Zoom gewählten User Experience als eine Software-Schwachstelle
    • Zoom verletzt damit die Erwartung von Nutzern, dass Kamera und Mikrofon nicht plötzlich für Fremde aktiviert werden, nur weil sie auf einen Link klicken
    • Wenn man Browser-Standard-Popups aus UX-Gründen vermeiden will, könnte die App stattdessen ein eigenes Popup anzeigen; Google Meet macht das gut vor
  • Schon das Ausführen eines Webservers auf localhost ist ein riskanter Ansatz, und insbesondere sollten privilegierte Funktionen wie Software-Installation nicht allen Websites im Internet zugänglich gemacht werden
    • CORS ist genau für solche Szenarien gedacht und sollte daher nicht umgangen werden

CORS-Verwirrung ist kein Zoom-spezifischer Fehler

  • Ob Zoom diesen Ansatz wirklich gewählt hat, weil man CORS nicht verstanden hat, ist nicht sicher
    • lerunicorn auf Reddit vermutet, dass Firefox XHR von sicheren zu unsicheren Ursprüngen blockieren könnte
    • Firefox unterstützt dies jedoch, wenn der Origin localhost ist
    • Native Apps können eigene selbstsignierte Zertifikate erzeugen und auch Browser-Erweiterungen verwenden
    • In keinem Fall ist das ein legitimer Grund, auf Origin-Filterung zu verzichten
  • CORS-Verwirrung ist kein Problem nur bei Zoom
  • Entwickler wollen ihren Code zum Laufen bringen, aber wer die Same-Origin-Policy komplett umgeht, legt wie im Fall Zoom lokale Berechtigungen für externe Websites offen
  • CORS-Verwirrung tritt sowohl bei erfahrenen als auch bei neuen Entwicklern auf; ob die CORS-API zu komplex ist oder ob Schulung zu CORS und CSP fehlt, ist unklar, aber der aktuelle Ansatz funktioniert offensichtlich nicht gut

1 Kommentare

 
GN⁺ 4 시간 전
Hacker-News-Kommentare
  • Offenbar hat auch der TFA CORS nicht richtig verstanden oder es stark falsch erklärt.
    Access-Control-Allow-Origin: https://zoom.us stellt nicht sicher, dass nur JavaScript von der Domain zoom.us mit dem localhost-Server kommunizieren kann. JavaScript von anderen Websites kann Anfragen an localhost:19421 genauso senden. CORS schränkt nichts ein, sondern lockert eine bestehende Einschränkung. Dieser Header erlaubt lediglich JavaScript, das auf zoom.us ausgeführt wird, die Antwort von localhost:19421 zu lesen; die Anfrage selbst findet ohnehin statt, daher muss das Backend so gebaut sein, dass keine Seiteneffekte entstehen.

    • Ich verstehe nicht, warum das der am höchsten bewertete Kommentar ist. Der OP hat recht, und die obige Erklärung ist falsch.
      GET-Anfragen werden zwar gesendet, sollten aber per Definition idempotent sein, sodass sie bei korrekt implementiertem Server keine Seiteneffekte verursachen können; bei GET ist entscheidend, ob die Antwort gelesen werden kann. Nicht-idempotente Anfragen, die Seiteneffekte haben können, schicken in Cross-Origin-Situationen dagegen zunächst eine preflight-OPTIONS-Anfrage statt der eigentlichen Anfrage; fehlen in der OPTIONS-Antwort die richtigen Header, wird die eigentliche Anfrage nicht gesendet.
    • Ich finde auch nicht, dass man sagen kann, CORS habe genau diese Aufgabe.
      Missverständnisse über CORS sind so weit verbreitet, und die Dokumentationen widersprechen sich oft, dass man kaum erwarten kann, dass eine unbekannte Gegenstelle es korrekt implementiert hat. Wenn ein Protokoll so viel Verwirrung stiftet, weiß man selbst dann nicht, ob die andere Seite korrekt funktioniert, wenn die eigene es tut. Wenn Leute ihren Code so lange angepasst haben, bis er mit anderen Implementierungen funktioniert, verschwimmt auch, ob der Fehler bei ihnen selbst oder bei der Gegenseite liegt.
    • So wie ich es verstanden habe, besteht der Hauptzweck von preflight OPTIONS darin, HTTP-Anfragen zu verhindern, die ursprünglich nicht erlaubt sind; bei ursprünglich erlaubten Anfragen tut CORS nichts.
      Zum Beispiel kann ein POST mit Content-Type text/json nicht ohne OPTIONS-Preflight an einen Drittanbieter-Host gesendet werden, ein POST mit multipart/form-data hingegen schon, und CORS blockiert das nicht. Wenn der Endpunkt dann Content-Type nicht strikt prüft und einfach JSON annimmt, kann jede Website ohne Interaktion des Nutzers POST-Anfragen senden.
    • „Nehmen wir an, wir sprechen nur über sichere Methoden“ ist eine ziemlich große Annahme.
      Ein ordentlicher Webentwickler sollte GET/HEAD/OPTIONS nicht den Zustand ändern lassen, und an einer Besprechung teilzunehmen ist eine Zustandsänderung. PUT/DELETE sollten ebenfalls idempotent sein. Eine POST-API mit einem anderen Format als JSON oder Formularen sollte den Content-Type-Header prüfen, und bei PUT/PATCH/DELETE sowie POSTs mit einem nicht formularbasierten Content-Type wird ein Preflight ausgelöst, sodass CORS geprüft wird, bevor die eigentliche Anfrage den Server erreicht.
    • Problematisch ist auch die Stelle im Artikel, dass „native Apps ein eigenes selbstsigniertes Zertifikat erzeugen können“.
      Es reicht nicht, nur ein Zertifikat zu erstellen; es muss als Root-CA-Zertifikat in allen Browser-Trust-Stores der Maschine installiert werden. Wenn der private Schlüssel der Root-CA nicht sauber geschützt wird, kann jede Website einen Man-in-the-Middle-Angriff durchführen, daher sind zumindest Namensbeschränkungen nötig (https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10). In Chrome funktionierte das bei Root-CAs jedoch bis v112 im Jahr 2023 nicht (https://alexsci.com/blog/name-non-constraint/), weshalb man eine Intermediate-CA hinzufügen und dort die Einschränkungen setzen musste. Natürlich sollte man den Root-CA-Schlüssel danach verwerfen.
      Ich habe früher einmal bei einem Projekt mit lokaler Root-CA Basic Constraints hinzugefügt, sie aber fälschlicherweise in die Root-CA gesetzt und nicht in allen Browsern getestet.
  • Ich wünschte, mehr Leute würden die CORS-Dokumentation von MDN lesen. Sie hat mir sehr geholfen, als ich CORS verstehen wollte, und ich hätte nicht gedacht, dass die Leute in den Kommentaren sich damit so schwertun.
    https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS

    • Dieses eine Dokument beantwortet für die meisten Fälle nicht nur einfache Origin-Beispiele, sondern auch wie Preflight funktioniert.
  • Schwer zu verstehen ist nicht nur CORS, sondern auch, dass viele Entwickler das Bedrohungsmodell nicht richtig verstehen.
    Selbst nach einer Erklärung ist oft nicht klar, warum das ein großes Problem ist. Gerade Backend-Entwickler konfigurieren häufig CORS, aber aus ihrer Sicht wirkt es nicht besonders wichtig, weil CORS kein Mechanismus zum Schutz von Zugriffsrechten ist. Für Angreifer scheint es, als könnten sie nichts mitnehmen, und aus Frontend-Sicht wirkt es leicht wie ein lästiges Hindernis. Dieser Artikel zeigt gute konkrete Beispiele.

    • Selbst in Projekten, in denen derselbe Entwickler sowohl Frontend als auch Backend geschrieben hat, habe ich schon CORS-Konfigurationen gesehen, die falsch waren.
      Als Betriebsverantwortlicher habe ich es dann am Load Balancer noch einmal korrekt eingerichtet, und zumindest funktioniert die Anwendung jetzt. CORS ist schwer zu verstehen, aber noch bedauerlicher ist, dass viele Entwickler nicht nur das Bedrohungsmodell hinter CORS, sondern die Webentwicklung insgesamt, insbesondere das HTTP-Protokoll, nicht verstehen.
    • Das CORS-Bedrohungsmodell ist gar nicht so schwierig. Es geht um eine Situation, in der ein Angreifer den Nutzer auf seine eigene Website lockt und ihn dazu bringt, auf deiner Website bestimmte Aktionen auszuführen.
    • CORS ist verwirrend, weil es auf einem ziemlich seltsamen Standard-Berechtigungsmodell aufsetzt. So nach dem Motto: multipart/form-data ist okay, aber Anwendungs-JavaScript nicht.
    • Aus Sicht von Angreifer und Verteidiger ist das Bedrohungsmodell nicht besonders intuitiv.
      CORS ist optional, und andere Bibliotheken oder Tools können es einfach ignorieren. CORS ist eigentlich nur sinnvoll, um XSS und CSRF bei tatsächlich eingeloggten menschlichen Nutzern zu verhindern; für andere Angriffsszenarien ist es bedeutungslos, weil dort ohnehin Skripte oder Programme verwendet werden, die HTTP-Header fälschen. Deshalb schalten Leute am Ende alle CORS-Optionen ein, was im schlimmsten Fall XSS und CSRF zulässt.
    • CORS eignet sich hervorragend dazu, zu verhindern, dass Leute Bandbreite und Hosting-Ressourcen einfach mitbenutzen. Wer das trotzdem stehlen will, muss einen eigenen Proxy aufsetzen, und der lässt sich dann leichter blockieren.
  • Dieser Kommentarbereich wirkt wirklich unerquicklich auf niedrigem Niveau und bestätigt eher genau den Punkt des Autors

    • Es könnte auch ein Generationenunterschied sein
      Wer schon vor CORS Webentwicklung gemacht hat, versteht, dass Cross-Domain-Requests ursprünglich verboten waren und CORS eingeführt wurde, um diese Beschränkung sicher zu umgehen. Deshalb liegt es nahe, einfach zu akzeptieren, dass man CORS aktivieren muss, wenn man etwas Bestimmtes erreichen will
      Wer dagegen erst nach CORS Webentwicklung gelernt hat, sieht nur den Ablauf: Man versucht einen Cross-Origin-Request, der Browser entscheidet, dass er nicht erlaubt ist, versucht einen CORS-Preflight, und wenn das fehlschlägt, erscheint ein CORS-Fehler in der Konsole. Wenn man die internen Abläufe nicht kennt und die Dokumentation nicht gelesen hat, vermutet man dann, dass CORS die Ursache dafür ist, dass die Anfrage blockiert wird, und versucht, „CORS zu deaktivieren“. Aber CORS ist nicht die Ursache des Problems, sondern die Lösung
      Wenn Leute mit demselben Missverständnis das dann in Tutorials und Online-Diskussionen selbstbewusst wiederholen, wird es nur noch verwirrender
    • CORS ist nicht intuitiv, aber wenn man die Dokumentation liest, kann man es verstehen
      https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
  • Beim Lesen der Kommentare war ich erleichtert zu sehen, dass nicht nur ich so denke. Niemand versteht CORS, weil es zu komplex ist und es zu viele Konflikte gibt
    Standards und Header ändern sich ständig, daher probieren Entwickler meist einfach Dinge aus, bis es funktioniert, deployen das Produkt und sind fertig. Selbst wenn es läuft, können in der Entwicklerkonsole noch Fehler und Warnungen stehen, aber solange es oberflächlich funktioniert, fasst man es lieber nicht mehr an

  • Um CORS zu verstehen, muss man zuerst die Same-Origin-Policy verstehen
    Vor allem wenn die Frage „Warum braucht man das überhaupt?“ schwerfällt, ist das ein guter Einstieg: https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy
    Ich habe die Same-Origin-Policy früher einmal als Interviewfrage verwendet, aber viele Bewerber waren damit nicht vertraut, sodass man aus dieser Frage nur wenig Information gewinnen konnte

    • Für die Einstellung von Frontend-Entwicklern ist das meiner Meinung nach eine ziemlich gute Frage
      Wer Web-Apps entwickelt hat, sollte irgendwann auf die Same-Origin-Policy gestoßen sein. Wenn nicht, fragt man meist weiter nach Dingen wie der Kommunikation mit dem Backend. Ob jemand schon einmal auf ein CORS-Problem gestoßen ist, aber nur den schnellsten Workaround angewendet und es dann vergessen hat, oder ob er wirklich versucht hat, es zu verstehen, ist für manche Rollen ein nützliches Signal
      Für Backend-Rollen ist die Frage weniger geeignet. Nicht jeder Backend-Entwickler hat eng mit Frontend-Teams gearbeitet, die häufig auf CORS-Probleme stoßen
  • Was mir zu CORS in Erinnerung geblieben ist: Debugging dauert viel länger als erwartet, die Browser-Fehlermeldungen sind absichtlich dürftig, und CORS-Fehler lassen sich anfangs nur schwer von anderen Fehlermodi unterscheiden

    • Ein CORS-Fehler ist keine „an den Browser gesendete Fehlermeldung“, sondern ein Fehler, den der Browser selbst erzeugt, weil er zu dem Schluss kommt, dass er die Anfrage nicht zulassen darf
      Wenn der Server natürlich CORS-Requests nicht versteht und merkwürdige Antworten zurückgibt, kann das am Ende trotzdem als CORS-Fehlschlag erscheinen
  • Der Kommentarbereich ist ziemlich unterhaltsam, daher noch ein Nachtrag: Die Same-Origin-Policy schützt davor, dass der Browser Informationen an Websites ohne Zugriffsrechte preisgibt, und CORS erlaubt es, diesen Schutz abzuschwächen
    Zum Beispiel verhindert die Same-Origin-Policy, dass example.com die Abonnementliste von youtube.com abrufen kann. Mit CORS kann man aber erlauben, dass example.com auf youtube.com/public/* zugreift
    Ein weiterer Einsatzzweck ist, dass ein Backend-API nicht einfach unter einem anderen Frontend betrieben werden kann und dadurch Datenabfluss entsteht. Es verhindert etwa Situationen, in denen man zwar beim echten Dienst eingeloggt ist, sich aber auf g00gle.com befindet und alle Anfragen per Man-in-the-Middle manipuliert werden könnten

    • Genauer gesagt ist es umgekehrt. Dieses Sicherheitsproblem verhindert die SOP, und CORS ist die Funktion, die die SOP lockert, um komplexere Interaktionen zwischen Anwendungen zu ermöglichen
  • Ich bin auch einer von denen. CORS ist ein Thema, das ich regelmäßig neu lernen muss, und ich vergesse es immer wieder, sodass es nie richtig hängen bleibt
    Vermutlich liegt es daran, dass ich Backend-Entwickler bin und fast nie mit CORS-Problemen zu tun habe. Dinge, die ich nicht täglich benutze, vergesse ich leicht

    • Die Developer Experience von CORS und CSP ist furchtbar. Browser sagen einem einfach nicht ordentlich, woher das Problem kommt
      In einer normalen Welt würde die Fehlermeldung Hinweise wie „Response-Header“ oder „meta-Tag“ enthalten, aber es wirkt, als hätten die großen Browserhersteller Leute eingestellt, die rätselhafte Meldungen schreiben. Das „requested resource“ in Chrome ist noch das Beste, aber immer noch kryptisch
      Bessere Meldungen wären zum Beispiel, dass die Ressource von https://bank.com keine CORS-Header hat und deshalb keine Cross-Origin-Requests erlaubt, oder dass der aktuelle Ursprung nicht auf der CORS-Allowlist steht. Dazu sollte auch der Preflight-Request im Netzwerk-Tab und ein MDN-Link angezeigt werden. Bei CSP wäre es ebenfalls besser, klar zu sagen, dass diese Seite die Ressource wegen des CSP-Headers auf dieser Seite nicht laden kann, und direkt zu den Request-Headern der Seite oder zum meta-Tag im Inspector zu verlinken
    • Das größte Problem bei CORS ist, dass die meisten Fehler wie ein Frontend-Problem aussehen, insbesondere wie ein Browserproblem, die eigentliche Korrektur aber im Backend erfolgen muss
    • Ich habe ein ähnliches Gefühl. Die Situationen, in denen ich mich mit CORS befassen musste, waren meist Anforderungen wie: „Wir müssen etwas von diesem Server holen, aber wir können die CORS- oder CSP-Einstellungen des Servers nicht ändern.“ In der Sprache der Sicherheit heißt das letztlich: „Es gibt ein Sicherheitssystem, und wir müssen es umgehen“
      Letztlich beruht das meist auf der Annahme, dass der Server nur über unveränderte Browser-Requests angesprochen wird. Die Zoom-Sicherheitslücke entstand auch deshalb, weil sich CORS und CSP clientseitig zu leicht umgehen lassen. Zoom war dabei sicher schlecht, faul und dumm, aber ich finde, auch die Community trägt Verantwortung, wenn sie dieses Modell weiter aufrechterhält
  • Ich verstehe, wie die Same-Origin-Policy verhindert, dass der Browser bösartige Skripte ausführt und Informationen exfiltriert. Ich verstehe auch, dass ein Server mit dem Header Access-Control-Allow-Origin erklärt, dass er zusätzlichen Origins vertraut und damit die SOP lockert
    Trotzdem verstehe ich den Zweck des Headers Access-Control-Allow-Headers noch nicht. Er scheint weder die Browsersicherheit zu verbessern noch erst recht die Serversicherheit. Ich frage mich, ob Protokolldesigner ihn der „Vollständigkeit halber“ aufgenommen haben. Dazu passend: https://stackoverflow.com/questions/17992042