RC RANDOM CHAOS

The same-origin policy is not protecting your API

Zoom encoded a localhost server's responses in image dimensions to dodge CORS, exposing it to every website on the internet.

· 3 min read
The same-origin policy is not protecting your API

Security researcher Jonathan Leitschuh found that Zoom ran a web server on the local machine at http://localhost:19421. When you loaded a Zoom link, the Zoom website sent a request to that local server telling it to open the native Zoom app. The interesting part is how the website read the server’s reply. Instead of a normal AJAX request, the page loaded an image from the local server, and the dimensions of that image encoded the status code, routed through a case-switch on the result.

That image trick exists to get around CORS. The browser’s same-origin policy stops JavaScript on zoom.us from reading the response to an arbitrary request against localhost. It does not stop the browser from loading an image from any origin and measuring its width and height. Encoding the answer in pixel dimensions sidesteps the policy completely.

Leitschuh’s writeup explained the hack by saying the browser ignores CORS for servers on localhost. That is wrong. Chrome respects CORS headers for localhost web servers. Anyone who has run Create React App with the frontend on one port and the API on another has made cross-origin requests against localhost and relied on the server’s CORS headers to allow them. Every browser supports this.

The likely story is that Zoom hit the browser blocking its cross-origin AJAX calls and, rather than configuring CORS on the local server, built the image workaround. The cost of that choice is that the local server answers to any origin. Not only can zoom.us trigger operations in the native client and read the response, every website on the internet can do the same.

A correct version of the feature is not complicated. The server on localhost:19421 should expose a REST API and set Access-Control-Allow-Origin: https://zoom.us, so only JavaScript served from zoom.us can talk to it. zoom.us should also send a Content Security Policy that blocks the page from rendering inside an iframe, so another site cannot embed it and open meetings silently in the background.

That still leaves one thing: any page can redirect your browser to a zoom.us meeting link you did not ask for. The author calls that a user-experience decision rather than a software vulnerability, and argues it is still the wrong call. Clicking a link should not suddenly make your camera and microphone available to people you do not know. If Zoom wants to avoid the browser’s own permission prompt for the sake of a smoother launch, it can put the prompt inside the app, the way Google Meet does.

The deeper problem sits under all of this. Running a web server on localhost that grants privileged operations, up to installing software, to whatever page is open in the browser is a risky thing to ship at all. CORS is the mechanism that lets you expose such a server to exactly one origin and no others. The failure here was working around that mechanism instead of configuring it.

People have floated a few motivations for the image approach. On reddit, lerunicorn pointed out that Firefox may block XHRs from a secure origin to a non-secure one, which could explain why an image felt easier than an AJAX call against http://localhost. Firefox allows those requests when the origin is localhost. A native app can also generate its own self-signed certificate, or Zoom could have shipped a browser extension. None of these is a reason to skip filtering origins.

This is not only a Zoom story. The author points to the volume of Stack Overflow answers that hand out wide-open CORS configuration, including an express snippet that would leave your application exposed if copied as written, and notes that other vendors have been caught shipping the same localhost pattern. Bypassing the same-origin policy gets the code working in the moment and leaves the hole for someone else to find later.

Share

Keep Reading

Latest on the Wire

Full wire →

New signal daily · RSS

Stay in the loop

New writing delivered when it's ready. No schedule, no spam.