WebSockets bypassing SOP/CORS
Tags: cors, sop, ws, wss
Published: 2021-07-24 00:16:42

## Same-Origin-Policy (SOP)

Same-Origin-Policy is a mechanism in browsers to protect the users from dynamically loaded document or script. It prevents running malicious scripts in browser to retrieve data from third party services. SOP specifies that dynamic requests can only be sent to URLs with the same Origin as the page they are requested from. Two URLs have the same origin if they share the same **_protocol_**, **_host_** and **_port_**. SOP assures that untrusted sites cannot perform authorized requests with the user's cookies, browser secrets or session identifiers by preventing requests to third parties. It only allows pages to call APIs in its own origin and blocks all other cross-origin requests. By default, modern browsers follow the SOP standards as a security mechanism.

Example: A user visiting `https://untrustedsite.com` prevents the site from calling `https://api.trustedsite.com`. The untrusted site pretends to call the trusted site on behalf of the user or without the user's permission. Such requests are blocked by SOP.

## Cross-Origin-Resource-Sharing (CORS)

While SOP is an important security measure, it also restricts legitimate cross-origin API calls. CORS relaxes the SOP so that it can be called from a trusted origin that is in a different domain than that of what is currently being called from. CORS policy informs the browser which domains are allowed to access the response object of a request, which HTTP methods are allowed and if cookies should be sent with requests. 
* The response header `Access-Control-Allow-Origin` specifies which origins are allowed to access the response objects from cross-origin requests.
* The `Access-Control-Allow-Credentials` specifies if cookies need to be attached. Boolean value.
* The `Access-Control-Allow-Methods` and `Access-Contol-Allow-Headers` header specifies which methods and headers are allowed.

All requests made from the browser should have a `Origin` header.

Example: If `Access-Control-Allow-Origin: https://trustedclient.com` is set on `https://api.trustedserver.com`. Only the trusted client is able to access the response from the server. Calls from all other origins are blocked by the SOP.

`Access-Control-Allow-Origin: *` is set to grant resource access to all domains. 

## Preflight requests
Since `Access-Control-Allow-Methods` and `Access-Contol-Allow-Headers` are response headers, there should first be a request made to fetch allowed methods and headers before sending out the actual request. Thus, browsers send a **__preflight request__**. The preflight request is an `OPTIONS` request with the headers `Access-Control-Request-Method` and `Origin`. The API then responds with an empty response (HTTP 204) with the appropriate response headers. If the API does not implement CORS then the browser falls back to SOP and the cross-origin request is denied. Requests to resources on the same origin as the requesting site, however, are not preflighted and are also not subjected to SOP. [Simple requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests) are also not preflighted, but do still require CORS headers to be set on response and will restrict access to response data if CORS policy does not allow.

## WebSocket protocols (WS/WSS) and Cross-Site WebSocket Hijacking (CSWSH)
Cross-origin restrictions imposed by SOP and CORS policies do not apply to WebSockets because those restrictions are placed on HTTP responses while WebSockets work over `WS`(WebScoket) or `WSS`(WebSocketSecure) protocols. To initiate a WebSocket connection, however, the client has to connect to the server over HTTP. Yet, SOP and CORS do not have any effect because no HTTP response data is required to complete WS handshake and data transfer afterwards is completely over the WebSocket protocol WS/WSS. The initial handshake occurs via `HTTP Upgrade` request, the response body is disregarded and the `HTTP/HTTPS` protocol is upgraded to `WS/WSS` protocol. Since CORS only restricts access to response and SOP cannot restrict access on WebSocket protocol, attackers could potentially establish a cross-origin WS connection and send malicious data or receive data from a subscribed channel. If a webserver supports WebSockets, an attacker could create a cross-origin WS connection to the server. Even though the server does not respond with CORS header, WebSocket connection will be established if the server supports WebSockets and sends a `101 Switching Protocol` status. From there on, the attacker can simply send and subscribe to data from the server. This is similar to `Cross-Site Request Forgery (CSRF)` but is extended from write-only CSRF attack to full read/write communication via WebSockets.  

## Upgrade HTTP to WebSocket

```
Connection: Upgrade
Upgrade: websocket
```

The `Upgrade` header field is used by clients to switch protocols with the server. The server responds with `101 Switching Protocols` response and the client-server can begin communicating in the new protocol. WebSockets is the most common use case for upgrading an HTTP connection.

## Securing WebSockets

Since SOP and CORS are ineffective for WebSockets, the server implementation should verify the `Origin` header on the `Upgrade` request to prevent cross-site WS connections. 
 
```javascript
const io = require("socket.io")(httpServer, {
  allowRequest: (req, callback) => {
    const isOriginValid = check(req);
    callback(null, isOriginValid);
  }
});
```
Authentication/authorization should also be performed while establishing connection eg. via token.

As a developer, it is necessary to be aware of this attack and to know how to prevent it.

Happy Hacking!