Understanding CORS and the Same-Origin Policy: Lessons from the Zoom Vulnerability
Many web developers struggle to understand Cross-Origin Resource Sharing (CORS) and the Same-Origin Policy (SOP), often treating it as a nuisance to be bypassed rather than a critical security boundary. This misunderstanding can lead to severe vulnerabilities, as demonstrated by a zero-day vulnerability in Zoom's native client communication.
The Zoom Vulnerability: A Case Study in CORS Misunderstanding
Zoom implemented a feature where their website could communicate with a local web server running on localhost:19421 to open the native Zoom app. Instead of using a standard REST API with proper CORS headers, Zoom used an "image hack"—loading an image from the local server where the image dimensions dictated the status code of the server.
This approach was used to bypass CORS because the developers believed that browsers explicitly ignore CORS policies for servers running on localhost. In reality, Chrome and other modern browsers do respect CORS headers for localhost web servers. By bypassing CORS via an image hack, Zoom opened a vulnerability where any website on the internet could trigger operations in the native client and access the response.
The Secure Implementation
To secure this feature, the local web server should have implemented a REST API and set the Access-Control-Allow-Origin header to https://zoom.us. This would ensure that only JavaScript running on the zoom.us domain could interact with the localhost web server. Additionally, a Content Security Policy (CSP) header should have been used to block the rendering of the zoom.us page within an iframe to prevent background operations.
Understanding the Same-Origin Policy (SOP) vs. CORS
A common point of confusion is the belief that CORS is a security feature that blocks requests. In fact, the Same-Origin Policy (SOP) is the primary security mechanism that prevents a document from one origin from reading data from another origin.
SOP: The Default Protection
SOP prevents a malicious site from fetching sensitive data from another site (e.g., preventing example.com from reading your YouTube subscriptions). It is important to note that SOP blocks the reading of the response, not the sending of the request. A request can still be sent and processed by a server, but the browser will prevent the JavaScript on the origin site from reading the response unless the server explicitly allows it.
CORS: The Controlled Relaxation of SOP
CORS is not a security feature that restricts access; rather, it is a mechanism to relax the SOP. It allows a server to tell the browser: "I trust this specific origin, so you may allow the JavaScript on this site to read the response of this request."
"CORS doesn't restrict anything, it loosens the default set of restrictions... That Access-Control-Allow-Origin header just allows JavaScript running on zoom.us to read the responses when it queries localhost:19421."
Why Developers Struggle with CORS
The difficulty in grasping CORS stems from several architectural and psychological factors:
- Inverted Security Model: Unlike traditional server-side security where the server is the arbiter of access, CORS relies on the browser to enforce the policy. The server provides the instructions, but the browser is the protector.
- Lack of Visibility: The threat model—a malicious third-party site acting on behalf of a user—is often invisible during development, making it feel like a hypothetical threat.
- Complexity and Legacy: The protocol is a patchwork of compromises for backward compatibility with HTML forms (e.g., the distinction between "simple requests" and preflight requests), making it less intuitive.
- Backend vs. Frontend Divide: Backend developers often view CORS as a configuration task for the frontend, while frontend developers view it as a browser error to be solved. This disconnect often leads to the following insecure defaults:
"Unfortunately, these [Stack Overflow examples] are often paired with pages that recommend very insecure defaults like this one in express which would make your application vulnerable if copied verbatim."
Best Practices for Avoiding CORS Issues
To avoid these pitfalls, developers should follow these core principles:
- Use Reverse Proxies: Hosting the backend on the same origin as the frontend (using a reverse proxy) eliminates the need for CORS entirely by adhering to the vanilla SOP.
- Avoid "Hacks" to Bypass SOP: Never use image-based or other non-standard workarounds to bypass the Same-Origin Policy. If a feature requires cross-origin communication, implement it using a standard REST API with a strictly defined
Access-Control-Allow-Originwhitelist. - Study the SOP First: To truly understand CORS, one must first understand the Same-Origin Policy. Without this foundation, CORS headers appear as arbitrary rules rather than a logical extension of a security boundary.