Tampering with WebSockets

I have been experimenting a bit with websockets, mostly to intercept and tamper with websocket traffic. In order to do so, I am using Jetty and the default chat-application which is bundled in the release ( > 7.0). I use google chrome as a browser.

My goal was to be able to intercept WebSocket communications between the server and the client, that is

  • Intercept messages that are sent to the server
  • Intercept messages from the server, before the client application receives them

I also want my solution to not depend on how the application uses websockets, only dependant on the browser API for WebSockets.

To send anything over websockets, a websocket is constructed and the send()-mehtod is called. Therefore, a simple interceptor would look like this:
window.WebSocket.prototype.oldSend = window.WebSocket.prototype.send;
window.WebSocket.prototype.send=function(data){
this.oldSend(prompt("Sending data",data));
}

Simple overloading of the send-function. That was easy.

It turns out however, that there is no similar functionality for receiving. To receive, an application developer must construct a WebSocket and then ‘monkey-patch’ onto it a function called “onmessage”. Llike so:

x=new WebSocket("ws://localhost:8080/ws/");
x.onmessage=function(){alert(message.data);}

This means that the prototype object never really comes into play, since the function is defined only for the actual websocket instance. With some help from Gareth Heyes at sla.ckers.org I instead used the __defineSetter__ property.

The __defineSetter__ is called whenever someone tries to set a property on an object. To get it called, however, I needed to create a wrapped WebSocket object which replaces window.WebSocket. By doing so, the websocket that is created by the application under review will be my ‘special’ websocket, where I can deflect any setting of onmessage and retain my tamper-enabled onmessage function.
var oldWebSocket=window.WebSocket;
function WrappedWebSocket(loc)
{
this.prototype=new oldWebSocket(loc);
this.__proto__=this.prototype;
var wrapper=this;
this.onmessage=function(message)
{
var data = prompt("Receiving data",message.data);
wrapper.trueonmessage({data:data});
}
this.__defineSetter__('onmessage', function(val){
wrapper.trueonmessage=val;
});
}
window.WebSocket=WrappedWebSocket

To finish up, I can merge the two tamper-functions into the same definition, and also (thanks Gareth) put it all inside a closure. The final version became:
window.WebSocket = function(oldWebSocket) {
return function WrappedWebSocket(loc)
{
this.prototype=new oldWebSocket(loc);
this.__proto__=this.prototype;
var wrapper=this;
this.onmessage=function(message)
{
var data = prompt("Receiving data",message.data);
wrapper.trueonmessage({data: data});
};
this.__defineSetter__('onmessage', function(val){wrapper.trueonmessage=val;});
this.send=function(data){this.prototype.send(prompt("Sending data",data));};
};
}(window.WebSocket);

And here is a bookmarklet that you can bookmark and execute the next time you want to tamper with a WebSocket-enabled app:TamperSocket

Leave a Reply