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