> The webserver listening in on localhost:19421 should implement a REST API and set a Access-Control-Allow-Origin header with the value https://zoom.us. This will ensure that only Javascript running on the zoom.us domain can talk to the localhost webserver.
No, that does not do that. JavaScript from any other website can still talk to localhost:19421 just the same. CORS doesn't restrict anything, it loosens the default set of restrictions (ignoring preflight requests for now and assuming we're talking just about "safe" Methods). That Access-Control-Allow-Origin header just allows JavaScript running on zoom.us to read the responses when it queries localhost:19421. The requests happen in any case, and you must ensure in your backend that they don't cause any adverse effects.
> The requests happen in any case, and you must ensure in your backend that they don't cause any adverse effects.
GET requests will be sent, but they are supposed to be idempotent so if your server is implemented in a sensible way, it cannot cause any adverse effect, and reading the response is all that matters for GET requests.
For non-idempotent requests however (the only ones that are supposed to be able to have side effects), a preflight OPTION request will be sent in cross-origin context instead of sending the request itself. And unless the right headers are set in the OPTION response, the request won't be sent at all.
This is not correct. Safety and idempotency are two different concepts. Safety is when a request does not result in a state change. Idempotency is when the outcome is the same whether you make the request once or several times.
GET is defined to be both safe and idempotent. Clients can make GET requests without explicit human intent driving them because they are safe, not because they are idempotent.
DELETE is not defined to be safe but it is defined to be idempotent. This means you need human intent to drive it, but clients can retry as much as they like because whether you ask to delete a resource one time or a hundred times, the result is still the same – the resource is deleted. Obviously making DELETE requests can result in adverse effects even though it is idempotent.
POST is neither safe nor idempotent, however it’s subject to some unintuitive rules when it comes to things like cross-origin requests for historical reasons.
> DELETE is not defined to be safe but it is defined to be idempotent
This one puzzles me though. How can DELETE be idempotent? If the first request works then the second one should return a 404, as the key to delete doesn't exist anymore.
The logic behind this is: It was possible to do this before the introduction of those APIs anyway - but you could not read the response.
You could send a GET request by, for example, embedding an image. And you could send a POST request by submitting a form - even if the form did only consist of hidden elements.
Those are called "simple requests": https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/COR...
It is expected from web application that they can correctly handle those requests, even when they were sent from a different origin (for example, blocking them by using a CSRF token).
It was however not expected that existing web applications could handle non-simple requests - as the ability to do so was restricted either to command line tools or required other user interaction/configuration. This is where the preflight request comes in - send an OPTIONS to check if the other side is okay with that kind of request.
But what CORS do is: It can make the response available to the requesting script - the request did already happen if it didn't require a preflight.
But CORS affect all other requests, e.g POST using JSON or XML content type, and all other methods like PUT, DELETE, PATCH.
So you can do an unsafe POST using form-encoded data, but if a server supports this, they hopefully mitigate CSRF, since this has always been a risk.
Just my first thought as a security engineer, but sounds like a perfect opportunity to execute a timing attack to me. For example, vheck which users exist (by measuring response time for /api/users?name=john) etc
So while a badly implemented GET handler can indeed cause security issues, this is old news and unrelated to CORS.
(Besides, why measure response times? Can’t you just check if api/users?name=john returns a resource or a 404 not found?)
lol
The degree to which CORS is poorly understood (I have read numerous (often contradictory) documentation and I don't really understand it.) means that you can't rely on it being implemented properly by an unknown party,
If a protocol reaches this level of widespread confusion, I think all bets are off. Even if one end of a system performs correctly, who's to say that the other will. If people adapt their code until it works with another implementation, were they mistaken, or the other end?
So there is only one end of the system that is confused - the servers - but at least the other end - the browsers - can mostly be trusted to implement it correctly.
(*) unless you're implementing an open proxy, but then you have bigger problems.
I think OPs point is that many of those rule sets probably don’t do what the author intended.
I would second that, because CORS questions are common and “turn on the allow all pattern” is almost always in the top 3 suggestions.
Semi-related tangent, it annoys the hell out of me that create-react-app (and the newer incarnation) don’t come with an “allow all” CORS rule. Don’t force me to figure out which arcane setting configures CORS headers, I’m the one writing the code, I’m okay with wherever the HTTP requests are going, I’ll set up real CORS headers on nginx for prod.
Including me TBH.
Back in the day, I was using Cloudhopper, a Twitter-developed library for the SMPP protocol (not to be confused with SMTP!). Protocols being protocols, there are strict limits on field sizes, defined on the actual protocol spec. I noticed that Cloudhopper didn't impose those limits, however.
Long story short, it turns out they just left out strictly imposing field limits because other implementations didn't care either. De facto has overruled de jura and the inmates are running the asylum!
The world seems to manage just well to get CORS to work, though. If developers fucking up implementations of any standard is enough justification to argue that something is worthless, you'd be hard pressed to find any software engineering topic that by your personal definition would be deemed worthless.
For example, a POST request with a Content-Type of "text/json" would not be allowed to be sent to third-party hosts without an OPTIONS preflight, but one with a Content-Type of "multipart/form-data" would be allowed and wouldn't be stopped by CORS at all, even to third-party hosts.
(And, of course, if your endpoint just assumes JSON without strictly checking the Content-Type, then congratulations, you've just allowed any website to POST to you, with no user action required.)
Is that so? Neither urlencoded forms nor multipart/form-data are valid JSON on the wire, so while other websites could send requests, wouldn't they just hit a parse error?
Source: I've done that successfully in multiple pentests.
Edit: lazy LLM generated example:
<form action="https://example.com/api" method="POST" enctype="text/plain">
<input name='{"key":"value", "ignore":"' value='"}'>
</form>
That gives you {"key":"value", "ignore":"="}
The trick is to stuff the = character you cannot control into an irrelevant value.An attacker might use JavaScript to set a "multipart/form-data" Content-Type (thereby bypassing the otherwise required OPTIONS preflight), but send JSON in the request body. Unless your web application specifically parses the body based on the Content-Type (web servers don't do this for you), then you wouldn't detect that.
That's a pretty big assumption. Any decent webdev should not let GET/HEAD/OPTIONS modify state (joining a meeting is changing state) and additionally PUT/DELETE should also be idempotent.
POST with JSON (or other non-form formats) api's should also have it's content-type header checked (text/plain forms can send a JSON body but the content-type will be text/plain). PUT/PATCH/DELETE and POST with a non-form content-type (application/x-www-form-urlencoded, multipart/form-data, or text/plain) will trigger a preflight so that CORS is properly checked before the actual request reaches the server.
> additionally PUT/DELETE should also be idempotent
Yes, but I think the majority of large web applications are not fully correct in terms of 'Safe and Idempotent Methods' (https://datatracker.ietf.org/doc/html/rfc9110#name-common-me...).
You can't ignore those because they constitute the bulk of CORS' security model.
Yes, you're technically right that CORS cannot prevent other websites from making any request to your server - this would be impossible, since the browser somehow has to get the CORS headers in the first place.
However what CORS absolutely lets you do is prevent requests to particular endpoints - and you can then design your API in such a way that the dangerous actions are only available behind those endpoints and thus make it safe.
I.e. what's missing in the TFA quote is that the server must also change the endpoint from GET to POST (in addition to setting the CORS headers) and remove the GET endpoint. Other websites would still be able to send a GET or a preflight OPTIONS request, but they wouldn't be able to send the actual POST request.
As such, Zoom's workaround had two problems: They didn't set any CORS headers, which prompted the browsers to only allow "safe", i.e. GET requests - and then put an unsafe action behind the endpoint, therefore violating the "safe" assumption. Moral of the story: Don't put actions that do something else than returning a result behind a GET request.
If a browser does not implement CORS protections (but allows cross-origin requests), then its users must have non-standard expectations about security.
> Further, native apps can generate a unique self-signed certificate.
Just creating a certificate will not work, unless it's installed as root CA certificates in all browser trust-stores on the machine. And if the private key of the root CA is not secured correctly, one could MitM any websites. So at least you want it name constrained (https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1....), but at least in Chrome until 2023 (v112) that did not work on root CA's (https://alexsci.com/blog/name-non-constraint/), so you had to add an intermediate CA and add the constrain there. Of course, you should also just throw away the key of the root CA.
I will admit I once added basic constrains in some project with a local root CA (2020-2022), but 'incorrectly' to the root CA, and did not test it in all browsers.
> No, that does not do that.
It restricts non-zoom.us domains to CORS-safe operations.
Which sometimes includes making the request, sometimes includes reading the response content or headers.