Developers don't understand CORS (2019)
The Gap in Developer Knowledge: Understanding CORS
Written by Chris Foster, this piece explores a recurring theme encountered during full-stack consulting: a widespread lack of comprehension regarding Cross-Origin Resource Sharing (CORS) among web developers.
The Zoom Case Study
A poignant example of this knowledge gap was highlighted by a vulnerability discovered in Zoom by security researcher Jonathan Leitschuh.
The Architecture
Zoom utilized a local web server listening at http://localhost:19421. The intended workflow was:
- User loads a Zoom link.
- The website sends a request to the local server.
- The local server triggers the native Zoom application.
The "Workaround"
Leitschuh noticed a strange implementation detail:
"I also found that, instead of making a regular AJAX request, this page instead loads an image from the Zoom web server that is locally running. The different dimensions of the image dictate the error/status code of the server."
The goal of this unusual method was to circumvent CORS restrictions. There is a common misconception that browsers ignore CORS for localhost. Actually, browsers just ignore it. Correction: Chrome specifically does respect CORS headers for localhost servers.
The Vulnerability Flow
Because Zoom bypassed the standard CORS mechanism, they inadvertently opened a security hole. Instead of only zoom.us being able to communicate with the local server, any website on the internet could now trigger operations in the native Zoom client.
Engineering a Secure Solution
To fix this, Zoom should have implemented a standard REST API with strict origin filtering.
The Technical Fix
The server at localhost:19421 should have utilized the following header:
Access-Control-Allow-Origin: https://zoom.us
This ensures that only scripts originating from the official domain can interact with the local server.
Security Checklist
- Implement a proper REST API.
- Set specific
Access-Control-Allow-Originvalues (avoid*). - Deploy a Content Security Policy (CSP) to prevent the site from being rendered in an
<iframe>.
Comparison: Implementation Approaches
| Feature | Zoom's Flawed Approach | Secure Implementation |
|---|---|---|
| Communication | Image dimension encoding | Standard REST API |
| Origin Control | Bypassed entirely | Access-Control-Allow-Origin |
| Framing | Unprotected | CSP frame-ancestors 'none' |
| Security | Vulnerable to any site | Restricted to zoom.us |
UX and Predictability
Zoom's desire for a "seamless" experience (opening the app immediately) clashed with a fundamental rule of UX: Software must be predictable.
If a user clicks a link, they should not expect their camera or microphone to be activated without warning. While Zoom avoided the browser's native popup for aesthetic reasons, they should have implemented an in-app confirmation, similar to how Google Meet operates.
The Broader Industry Problem
Running a server on localhost is inherently risky. It should never provide privileged access (like software installation) to the open web.
Some argue that Firefox's restrictions on XHR requests from secure (HTTPS) to non-secure (HTTP) origins might have pushed developers toward these hacks. However, since native apps can generate self-signed certificates, this is no excuse for ignoring origin filtering.
The "Stack Overflow" Effect
Many developers rely on snippets that prioritize "making it work" over security. For example, some Express.js configurations suggest:
// WARNING: This is insecure!
app.use(cors({
origin: '*'
}));
This * wildcard allows any origin to access the resource, which is often copied verbatim into production environments.
Conclusion
The confusion surrounding CORS and CSP exists across all experience levels. This leads to a critical question:
Regardless of the cause, the current state of developer education is insufficient, as evidenced by the vulnerabilities found in major software like Zoom.