Skip to content

Commit

Permalink
debugger: validate sec-websocket-accept response header
Browse files Browse the repository at this point in the history
This addresses a TODO to validate that the sec-websocket-accept header
in the WebSocket handshake response is valid. To do this we need to
append the WebSocket GUID to the original key sent in sec-websocket-key,
sha1 hash it, and then compare the base64 encoding with the value sent
in the sec-websocket-accept response header.

If they don't match, an error is thrown.

PR-URL: #39357
Refs: nodejs/node-inspect#93
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
  • Loading branch information
copperwall authored and targos committed Sep 4, 2021
1 parent 43d28f5 commit 482459e
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 4 deletions.
26 changes: 22 additions & 4 deletions lib/internal/debugger/inspect_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
} = primordials;

const Buffer = require('buffer').Buffer;
const crypto = require('crypto');
const { ERR_DEBUGGER_ERROR } = require('internal/errors').codes;
const { EventEmitter } = require('events');
const http = require('http');
Expand All @@ -35,13 +36,30 @@ const kTwoBytePayloadLengthField = 126;
const kEightBytePayloadLengthField = 127;
const kMaskingKeyWidthInBytes = 4;

// This guid is defined in the Websocket Protocol RFC
// https://tools.ietf.org/html/rfc6455#section-1.3
const WEBSOCKET_HANDSHAKE_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

function unpackError({ code, message, data }) {
const err = new ERR_DEBUGGER_ERROR(`${message} - ${data}`);
err.code = code;
ErrorCaptureStackTrace(err, unpackError);
return err;
}

function validateHandshake(requestKey, responseKey) {
const expectedResponseKeyBase = requestKey + WEBSOCKET_HANDSHAKE_GUID;
const shasum = crypto.createHash('sha1');
shasum.update(expectedResponseKeyBase);
const shabuf = shasum.digest();

if (shabuf.toString('base64') !== responseKey) {
throw new ERR_DEBUGGER_ERROR(
`WebSocket secret mismatch: ${requestKey} did not match ${responseKey}`
);
}
}

function encodeFrameHybi17(payload) {
var i;

Expand Down Expand Up @@ -292,8 +310,8 @@ class Client extends EventEmitter {
_connectWebsocket(urlPath) {
this.reset();

const key1 = require('crypto').randomBytes(16).toString('base64');
debuglog('request websocket', key1);
const requestKey = crypto.randomBytes(16).toString('base64');
debuglog('request WebSocket', requestKey);

const httpReq = this._http = http.request({
host: this._host,
Expand All @@ -302,7 +320,7 @@ class Client extends EventEmitter {
headers: {
'Connection': 'Upgrade',
'Upgrade': 'websocket',
'Sec-WebSocket-Key': key1,
'Sec-WebSocket-Key': requestKey,
'Sec-WebSocket-Version': '13',
},
});
Expand All @@ -319,7 +337,7 @@ class Client extends EventEmitter {
});

const handshakeListener = (res, socket) => {
// TODO: we *could* validate res.headers[sec-websocket-accept]
validateHandshake(requestKey, res.headers['sec-websocket-accept']);
debuglog('websocket upgrade');

this._socket = socket;
Expand Down
1 change: 1 addition & 0 deletions src/node_native_module.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ void NativeModuleLoader::InitializeModuleCategories() {
std::vector<std::string> prefixes = {
#if !HAVE_OPENSSL
"internal/crypto/",
"internal/debugger/",
#endif // !HAVE_OPENSSL

"internal/bootstrap/",
Expand Down

0 comments on commit 482459e

Please sign in to comment.