Overview
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
One of the cornerstones of the JavaScript security model is the same origin policy. This means that if a Script was downloaded from http://host1.example.com:8000, the script can only create connections to host.1example.com port 8000. Not port 8001 and not host2.example.com.
However there are two functions in JavaScript that allow to circumvent this limitation: XMLHttpRequest and WebSocket. These can be abused to get an insight into the victim’s network and even to attack web servers, that are on the inside.
WebSockets
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.
Taking Aim
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.
Luckily newer versions of Firefox and Chrome contain a video-chat functionality called WEBRTC. A part of this functionality is to find the reachable IP-Address behind NAT-Gateways and pack all the IP-Addresses in one string that JavaScript can work with. To do this a connection to a STUN server on the Internet is tried. The following JavaScript code does the trick for Firefox:
1 2 3 4 5 6 7 8 9 |
|
The variable
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 1.2.3.4 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()
While the impact of using WebSockets to find internal reachable hosts classifies as a nuisance and does not pose an immediate threat to the network (unless there are WebSocket services running) there is a second method that allows the circumvention of the same origin policy as well. This is XMLHTTPRequest. This function is used to build complex requests between a JavaScript application in the browser and the web server. It allows to create POST requests and set custom headers. Basically an attacker can create any request type from the OWASP top ten list using XMLHTTPRequest. If it is used against a URL that violates the same origin policy an error is triggered that depending on the browser even says “You are not allowed to do that”. However the request is executed as one can verify if the target chosen is a web server where the log files can be monitored. So while the attacker does see the result of the attack it might still be successful.
This was reported as an error to Microsoft, Mozilla and the Chromium team. All three closed the error mentioning, that this is according to the CORS specification of the W3C. And indeed in this specification it is stated that it should be allowed. Application can protect themselves by verifying the ‘Origin’ header field in the HTTP Request that points to the URI where the JavaScript code executing the request was downloaded from. If it is an allowed origin the request is processed by the server or else denied with an appropriate error code.
While what the W3C is proposing there is a nice theoretical construct if all web applications or at least web servers would be developed from now on having the Origin header in mind this poses a high risk in a typical LAN where there are lots of unpatched old web applications and web servers a little bit of JavaScript can wreak havoc. Setting this behavior as a standard is quite naive considering real world deployments.
The
scan()routing can be replace by an XMLScan Routine, as the same method of timing works in the error handler if XMLHTTPRequest as well.
Conclusion/Recommendation
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.
Written by
Konstantin Agouros