Skip to content

Instantly share code, notes, and snippets.

@c0nrad

c0nrad/writeup.md Secret

Last active Nov 20, 2017
Embed
What would you like to do?

https://nodesecurity.io/advisories/67

@xc0nradx

@nodesecurity @feross @mafintosh

Description

The writeup implies that the websocket server will receive the number 50, and by type confusion accidentally allocate an uninitialized buffer of size 50. The buffer will then be sent to the client as the pong response. Using this bug, a malicious actor can potentially read memory off vulnerable servers.

But, after playing around with the PoC, I don’t think this is correct. I think the actual problem is in the client. If you track down client.ping, you’ll see that calling client.ping(50) actually allocates the buffer and sends it to the server. So the server is simply responding with the clients memory buffer. The server never allocates 50 bytes of uninitialized memory and sends it back to the client. It's doing exactly what it should be doing.

To prove this, we're going to print out exactly what the client is sending, and what the server receives.

Demo

Using ws: "1.0.0"

Server PoC:

var ws = require('ws').Server;

var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({ port: 8085 });

wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(message) {
    console.log('received: %s', message);
  });

  ws.send('something');
});

console.log("websocket server listening on :8085")

Client PoC:

var ws = require('ws')

var client = new ws('ws://demo.server.com:8085')

client.on('open', function () {
  console.log('open')
  client.ping(50) // this makes the server return a non-zeroed buffer of 50 bytes

  client.on('pong', function (data) {
    console.log('got pong');
    console.log(data) // a non-zeroed out allocated buffer returned from the server
  })
})

Then modify line 647 of the server’s ws package to print out the unmasked data. https://github.com/websockets/ws/blob/753937ff1ddc0938513267b4d6d5139a6ad41746/lib/Receiver.js#L647

Then modify line 158 of the client’s ws package to print out the payload before it is sent to the server. https://github.com/websockets/ws/blob/753937ff1ddc0938513267b4d6d5139a6ad41746/lib/Sender.js#L158

Running the PoC you should see something similar to: example

This means that the problem was actually in client.ping, not the server’s pong response.

The Fix

Even though the advisory is wrong, the fix is still correct. https://github.com/websockets/ws/commit/29293ed11b679e0366fa0f6bb9310b330dafd795

The fix converts the client.ping(50) into essentially client.ping(‘50’) inside of the sender.

Contact

Stuart Larsen, Yahoo Paranoids stuartlarsen@yahoo-inc.com

@c0nrad

This comment has been minimized.

Copy link
Owner Author

@c0nrad c0nrad commented Jan 6, 2016

I also attempted to exploit just the server using a golang client implementation. I believe everything server side is coerced to type Buffer. So there was no real vulnerability. (except for clients potentially spewing their memory all over the place).

golang PoC:
https://gist.github.com/c0nrad/dd05a3a99d9c159f3d40

@feross

This comment has been minimized.

Copy link

@feross feross commented Jan 6, 2016

You're correct. The disclosure, as currently written, overstates the severity of the problem.

Still, it's important that this was fixed because it might be possible to get a server to call one of these:

socket.send(number)
socket.ping(number)
socket.pong(number)

especially if the socket server has some echo functionality:

server.on('connection', function (socket) {
  socket.on('message', function (message) {
    message = JSON.parse(message)
    if (message.type === 'echo') {
      socket.send(message.data)
    }
  })
})

socket.send(number) called on the server, will disclose server memory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment