Why is there no same-origin policy for WebSockets? Why can I connect to ws://localhost?
I'd like to use WebSockets for inter-process communication for my application (Daemon<->WebGUI and Daemon<->FatClient, etc.). During testing, I tried connecting to my locally running web socket server (ws://localhost:1234) via the JavaScript WebSocket client on websocket.org (http://www.websocket.org/echo.html).
My question now is:
Why is this possible? Is there no cross-origin policy implemented in the browsers (here: FF29 on Linux)?
I am asking because if websocket.org was evil, it could try to communicate with my local WS server and redirect every message it receives from localhost to any other server:
Local WebSocket Server Browser Evil Web Server at ws://localhost:1234 at http://evil.tld | | | | |------[GET /]--------->| | |<-----[HTML+EvilJS]----| |<------[connect ws://..]----| | |<----[some communication]-->| | | |----[evil forward]---->| | | |
I have not tested the entire use case, but the connect to ws://localhost from the JS delivered by websocket.org definitely works.
Solution 1:
To address the "Why?" part, the reason why browsers don't enforce the Same Origin Policy (of which CORS is a relaxation) for WebSockets as opposed to AJAX calls, is because WebSockets were introduced after the value of cross-origin requests was established, and because they're not subject to SOP to begin with, the historical reason for the CORS client-side checks doesn't apply.
For AJAX, in the days of a blanket Single Origin Policy, servers never expected an authenticated browser to send a request from a different domain1, and so didn't need to ensure the request was coming from a trusted location2, just check the session cookie. Later relaxations like CORS had to have client-side checks to avoid exposing existing applications to abuse by violating this assumption (effectively doing a CSRF attack).
If the Web were being invented today, knowing what we know now, neither SOP nor CORS would be required for AJAX and it's possible that all the validation would be left to the server.
WebSockets, being a newer technology, are designed to support cross-domain scenarios from the get go. Anyone writing server logic should be aware of the possibility of cross-origin requests and perform the necessary validation, without the need for heavy-handed browser-side precautions à la CORS.
1 This is a simplification. Cross-origin GET requests for resources (including <img>, <link> and <script> tags) and form submission POST requests were always permitted as a fundamental feature of the Web. Nowadays, cross-origin AJAX calls whose requests have the same properties are also permitted and known as simple cross-origin requests. However, accessing the returned data from such requests in code is not allowed unless explicitly permitted by the server's CORS headers. Also, it is these "simple" POST requests that are the primary reason why anti-CSRF tokens are necessary for servers to protect themselves from malicious websites.
2 In fact, a secure way to check the request source wasn't even available since the Referer
header can be spoofed e.g. using an open redirect vulnerability. This also shows how poorly CSRF vulnerabilities were understood back then.
Solution 2:
oberstet answered the question. Thank you! Unfortunately I can't mark it as "correct" because it was a comment. The browser sends the "origin" header which can be checked by the application.
In Java [1]:
@Override public void onOpen(WebSocket clientSocket, ClientHandshake handshake) { String clientOrigin = handshake.getFieldValue("origin"); if (clientOrigin == null || !clientOrigin.equals(WEBSOCKET_ALLOWED_ORIGIN_HEADER)) { logger.log(Level.WARNING, "Client did not sent correct origin header: " + clientOrigin); clientSocket.close(); return; } // ... }
[1] using https://github.com/TooTallNate/Java-WebSocket
Solution 3:
WebSockets can cross domain communication, and they are not limited by the SOP (Same Origin Policy).
The same security issue you described can happen without WebSockets.
The evil JS can:
- Create a script/image tag with a URL to evil.tld and put data in the query string.
- Create a form tag, put the data in the fields, and invoke the "submit" action of the form, doing an HTTP POST, that can be cross domain. AJAX is limited by the SOP, but normal HTTP POST is not. Check the XSRF web security issue.
If something injects javascript in your page, or you get malicious javascript, your security is already broken.