This text describes risks that are introduced by current browsers and are not vulnerabilities caused by software bugs, but are implemented this way to comply with standards of the W3 Consortium.
Same origin Policy
WebSockets are a means of asynchronous communication between a browser and a server. Instead of the usual cycle: Browser sends a request, Server sends an answer it establishes a two way connection where each side can send data and the other can receive it which makes application development much easier.
A WebSocket is created by passing a WebSocket uri as argument to the instantiation of a new WebSocket. The uris have the format “ws://host:port”. wss is used as protocol, if the connection shall be encrypted. What happens underneath the hood is that a connection is created to host:port which is expected to be a web server. This connection is then ‘upgraded’ to a WebSocket, if the server understands the WebSocket protocol. If a web proxy is in between client and server the connection shows up as a CONNECT request.
An error handler can be attached to the WebSocket that is triggered if something goes wrong when trying to connect to the server. WebSockets completely ignore the same origin policy so the script can connect anywhere it wants. The impact of this is limited as only services that provide websockets can be contacted in a meaningful way. However if there are any in the LAN, a rogue script can attack these.
This error handler can be used to scan, which internal hosts are reachable. Unfortunately the error handler does not give a reason why the connection attempt failed (nothing running on this port, host not reachable, something running on this port which isn’t a WebSocket service). However if the script measures the time between the attempt to connect and the occurrence of the error it can decide if the target host was reachable (error occurs very quickly) or the host is not reachable (we have to wait for the TCP timeout before the error is triggered). The following sample code assumes a function that produces an IP address to be tested for each call of getNextIP(). The function scan than tests, measures the time and stores the results in a hash that can later be analyzed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
The result of this scan is a list of pingable hosts. And if there are actual hosts that speak WebSocket, these are identified as well.
The function above needs a list of IP-Addresses to scan. If these are not known beforehand, the RFC1918 address ranges are a good candidate. However scanning all of there would take too long as due to the asynchronous nature of waiting for the error handler this scan is rather slow. To make it easier it would be helpful to know the subnet in which the victim browser is located to limit the address ranges to probe based on this information.
1 2 3 4 5 6 7 8 9
descriptionafterwards contains information like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
In this example addresses in the 10 and 192.168. ranges are private and 220.127.116.11 stands for the IP address that this host appears on on the Internet. With a bit of parsing, the RFC1918 addresses can be extracted and a list for the getNextIP function used in scan() can be compiled.
It gets worse: XMLHTTPRequest()
scan()routing can be replace by an XMLScan Routine, as the same method of timing works in the error handler if XMLHTTPRequest as well.
There are a few mitigations that can be recommended:
There are two pieces of demo code attached to this blog entry. The first allows You to create an XMLHTTP-Request against Your own internal web server. So You can verify, that a script on this externally loaded page can access any of Your web servers if You press the button.
The second does a scan of the RFC1918 LAN(s) that Your browser is connected to and shows the results in Your browser. This only works with Firefox 22 or newer.