为什么没有针对 WebSocket 的同源策略? 为什么我可以连接到 ws://localhost?

我希望使用 WebSocket 为我的应用程序提供行程间通讯(Daemon <-> WebGUI 和 Daemon <-> FatClient,等等)。在测试期间,我尝试通过 WebSocket.org (http://www.websocket.org/echo.html)上的 JavaScript WebSocket 客户端连接到本地运行的 web socket 服务器(ws://localhost: 1234)。

我现在的问题是:
为什么这是可能的? 在浏览器中没有实现跨源策略(这里: Linux 上的 FF29) ?

我这么问是因为如果 websocket.org 是邪恶的,它可能会尝试与我的本地 WS 服务器通信,并将它从本地主机收到的每条消息重定向到任何其他服务器:

Local WebSocket Server            Browser            Evil Web Server
at ws://localhost:1234                               at http://evil.tld
|                            |                       |
|                            |------[GET /]--------->|
|                            |<-----[HTML+EvilJS]----|
|<------[connect ws://..]----|                       |
|<----[some communication]-->|                       |
|                            |----[evil forward]---->|
|                            |                       |

我还没有测试整个用例,但是通过 websocket.org 交付的 JS 连接到 ws://localhost 肯定是可以工作的。

49503 次浏览

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.

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

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.