Developers don’t understand CORS – Chris Foster

Developers don’t understand CORS

July 10, 2019 – Chris Foster

One of the best things about working at a full stack consultancy is that I get the chance to work with a large number of developers with different skill levels in companies of different sizes and industries. This provides an opportunity to see what universal conflicts come up. What seems common and relevant lately is this: Many web developers don’t understand how CORS works.

This seems especially pertinent to point out because of the recent Zoom vulnerability. Security researcher Jonathan Leitschuh discovered that Zoom has a web server listening on the machine http://localhost:19421. When you load a Zoom link, Zoom’s website sends a request to the localhost webserver asking it to open the native Zoom app. The entire article is worth reading, but these parts stuck out to me:

I also noticed that, instead of making a regular AJAX request, this page loads an image from a Zoom web server running locally. The different dimensions of the image determine the server’s error/status code. You can see that case-switch logic here.

images

One question I asked is why is this web server returning this data encoded in the dimensions of an image file? The reason for this is that this is done to bypass Cross-Origin Resource Sharing (CORS). For very deliberate reasons, the browser explicitly ignores any CORS policy for servers running on localhost.

That last sentence is incorrect – Chrome respects CORS headers for the localhost webserver. If you’re a web developer you’ve probably done this when you have your frontend app on one port and a React app with your backend API on another port. Your app is making cross origin requests against localhost, and this is supported in all browsers.

What this tells me is that Zoom may need to take this feature out and didn’t understand CORS. They could not make an AJAX request without the browser rejecting the attempt. Instead, he created this image hack to do the job nearby CORS. By doing this, they opened Zoom up to a huge vulnerability because not only could the Zoom website trigger operations in the native client and access the response, but every other website on the Internet could do the same.

So what would a secure implementation of this feature look like? webserver listening localhost:19421 Must implement a REST API and set up a Access-Control-Allow-Origin header with value https://zoom.us. This will ensure that only JavaScript running on the Zoom.us domain can talk to the localhost webserver. Additionally, to prevent pages from automatically opening Zoom meetings in the background, Zoom.US must have a Content Security Policy header that blocks rendering within an iframe.

This still leaves the vulnerability that any page could redirect your browser to a Zoom.us link for a meeting you weren’t expecting, but this is a user experience decision Zoom made rather than a software vulnerability. Personally, I think the approach is wrong here too. They mentioned that they want a better user experience by opening applications directly, but one of the rules of good user experience design is that your software should be predictable.

If I’m clicking on a link, I expect it not to suddenly make my camera and microphone available to people I don’t know. Zoom is breaking this expectation. Even if they don’t want a built-in browser popup for UX reasons, put this popup in the app! Google Meet does this well:

meet

I don’t want to take away from the CORS focus of this post. Despite the user experience side of the argument, running a webserver on localhost is a risky endeavor in the beginning. It should not provide privileged access to functions at all, such as installing softwareFor every website on the Internet. CORS enables you to do this safely – don’t hack around it!

I can’t know for sure whether Zoom implemented this feature this way due to a failure to understand CORS. However, I’ve talked to a few people and none of us could collectively find any valid reason to implement our current approach. On Reddit, lerunicorn found and suggested that Firefox may block XHRs from secure to non-secure origins which may explain the motivation behind this approach. However, Firefox supports it when the origin is localhost. Additionally, native apps can generate a unique self-signed certificate. Alternatively, they could use browser extensions. In any case, this is not a valid reason to forget to filter the origin.

It’s not just Zoom. Anecdotally, a lot of developers I’ve talked to don’t understand very well how CORS works. Examples of questions are also very abundant on StackOverflow. Unfortunately, these are often coupled with pages that recommend such very insecure defaults in Express that would make your application insecure if copied verbatim. Other vendors have been caught with exactly the same vulnerability found in Zoom.

Developers just want to get their code to work, and bypassing the same-origin policy entirely can get it to work, but when someone finds out what you’ve done you’ll get problems like Zoom.

I’ve seen CORS confusion from both experienced and new developers. Is the CORS API too complex and confusing, or do we just need better developer education around issues like CORS and CSP? I’m not sure, but the current approach certainly doesn’t seem like it’s working.



<a href

Leave a Comment