Skip to content

Commit aca94c8

Browse files
committed
[fix] Abort the handshake if an unexpected extension is received
Abort the handshake if the client receives a `Sec-WebSocket-Extensions` header but no extension was requested. Also abort the handshake if the server indicates an extension not requested by the client.
1 parent 38c6c73 commit aca94c8

File tree

2 files changed

+165
-13
lines changed

2 files changed

+165
-13
lines changed

lib/websocket.js

+40-12
Original file line numberDiff line numberDiff line change
@@ -660,22 +660,50 @@ function initAsClient(websocket, address, protocols, options) {
660660

661661
if (serverProt) websocket._protocol = serverProt;
662662

663-
if (perMessageDeflate) {
663+
const secWebSocketExtensions = res.headers['sec-websocket-extensions'];
664+
665+
if (secWebSocketExtensions !== undefined) {
666+
if (!perMessageDeflate) {
667+
const message =
668+
'Server sent a Sec-WebSocket-Extensions header but no extension ' +
669+
'was requested';
670+
abortHandshake(websocket, socket, message);
671+
return;
672+
}
673+
674+
let extensions;
675+
664676
try {
665-
const extensions = parse(res.headers['sec-websocket-extensions']);
677+
extensions = parse(secWebSocketExtensions);
678+
} catch (err) {
679+
const message = 'Invalid Sec-WebSocket-Extensions header';
680+
abortHandshake(websocket, socket, message);
681+
return;
682+
}
683+
684+
const extensionNames = Object.keys(extensions);
685+
686+
if (extensionNames.length) {
687+
if (
688+
extensionNames.length !== 1 ||
689+
extensionNames[0] !== PerMessageDeflate.extensionName
690+
) {
691+
const message =
692+
'Server indicated an extension that was not requested';
693+
abortHandshake(websocket, socket, message);
694+
return;
695+
}
666696

667-
if (extensions[PerMessageDeflate.extensionName]) {
697+
try {
668698
perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
669-
websocket._extensions[PerMessageDeflate.extensionName] =
670-
perMessageDeflate;
699+
} catch (err) {
700+
const message = 'Invalid Sec-WebSocket-Extensions header';
701+
abortHandshake(websocket, socket, message);
702+
return;
671703
}
672-
} catch (err) {
673-
abortHandshake(
674-
websocket,
675-
socket,
676-
'Invalid Sec-WebSocket-Extensions header'
677-
);
678-
return;
704+
705+
websocket._extensions[PerMessageDeflate.extensionName] =
706+
perMessageDeflate;
679707
}
680708
}
681709

test/websocket.test.js

+125-1
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,40 @@ describe('WebSocket', () => {
663663
});
664664
});
665665

666-
it('fails if the Sec-WebSocket-Extensions response header is invalid', (done) => {
666+
it('fails if an unexpected Sec-WebSocket-Extensions header is received', (done) => {
667+
server.once('upgrade', (req, socket) => {
668+
const key = crypto
669+
.createHash('sha1')
670+
.update(req.headers['sec-websocket-key'] + GUID)
671+
.digest('base64');
672+
673+
socket.end(
674+
'HTTP/1.1 101 Switching Protocols\r\n' +
675+
'Upgrade: websocket\r\n' +
676+
'Connection: Upgrade\r\n' +
677+
`Sec-WebSocket-Accept: ${key}\r\n` +
678+
'Sec-WebSocket-Extensions: foo\r\n' +
679+
'\r\n'
680+
);
681+
});
682+
683+
const ws = new WebSocket(`ws://localhost:${server.address().port}`, {
684+
perMessageDeflate: false
685+
});
686+
687+
ws.on('open', () => done(new Error("Unexpected 'open' event")));
688+
ws.on('error', (err) => {
689+
assert.ok(err instanceof Error);
690+
assert.strictEqual(
691+
err.message,
692+
'Server sent a Sec-WebSocket-Extensions header but no extension ' +
693+
'was requested'
694+
);
695+
ws.on('close', () => done());
696+
});
697+
});
698+
699+
it('fails if the Sec-WebSocket-Extensions header is invalid (1/2)', (done) => {
667700
server.once('upgrade', (req, socket) => {
668701
const key = crypto
669702
.createHash('sha1')
@@ -693,6 +726,97 @@ describe('WebSocket', () => {
693726
});
694727
});
695728

729+
it('fails if the Sec-WebSocket-Extensions header is invalid (2/2)', (done) => {
730+
server.once('upgrade', (req, socket) => {
731+
const key = crypto
732+
.createHash('sha1')
733+
.update(req.headers['sec-websocket-key'] + GUID)
734+
.digest('base64');
735+
736+
socket.end(
737+
'HTTP/1.1 101 Switching Protocols\r\n' +
738+
'Upgrade: websocket\r\n' +
739+
'Connection: Upgrade\r\n' +
740+
`Sec-WebSocket-Accept: ${key}\r\n` +
741+
'Sec-WebSocket-Extensions: ' +
742+
'permessage-deflate; client_max_window_bits=7\r\n' +
743+
'\r\n'
744+
);
745+
});
746+
747+
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
748+
749+
ws.on('open', () => done(new Error("Unexpected 'open' event")));
750+
ws.on('error', (err) => {
751+
assert.ok(err instanceof Error);
752+
assert.strictEqual(
753+
err.message,
754+
'Invalid Sec-WebSocket-Extensions header'
755+
);
756+
ws.on('close', () => done());
757+
});
758+
});
759+
760+
it('fails if an unexpected extension is received (1/2)', (done) => {
761+
server.once('upgrade', (req, socket) => {
762+
const key = crypto
763+
.createHash('sha1')
764+
.update(req.headers['sec-websocket-key'] + GUID)
765+
.digest('base64');
766+
767+
socket.end(
768+
'HTTP/1.1 101 Switching Protocols\r\n' +
769+
'Upgrade: websocket\r\n' +
770+
'Connection: Upgrade\r\n' +
771+
`Sec-WebSocket-Accept: ${key}\r\n` +
772+
'Sec-WebSocket-Extensions: foo\r\n' +
773+
'\r\n'
774+
);
775+
});
776+
777+
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
778+
779+
ws.on('open', () => done(new Error("Unexpected 'open' event")));
780+
ws.on('error', (err) => {
781+
assert.ok(err instanceof Error);
782+
assert.strictEqual(
783+
err.message,
784+
'Server indicated an extension that was not requested'
785+
);
786+
ws.on('close', () => done());
787+
});
788+
});
789+
790+
it('fails if an unexpected extension is received (2/2)', (done) => {
791+
server.once('upgrade', (req, socket) => {
792+
const key = crypto
793+
.createHash('sha1')
794+
.update(req.headers['sec-websocket-key'] + GUID)
795+
.digest('base64');
796+
797+
socket.end(
798+
'HTTP/1.1 101 Switching Protocols\r\n' +
799+
'Upgrade: websocket\r\n' +
800+
'Connection: Upgrade\r\n' +
801+
`Sec-WebSocket-Accept: ${key}\r\n` +
802+
'Sec-WebSocket-Extensions: permessage-deflate,foo\r\n' +
803+
'\r\n'
804+
);
805+
});
806+
807+
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
808+
809+
ws.on('open', () => done(new Error("Unexpected 'open' event")));
810+
ws.on('error', (err) => {
811+
assert.ok(err instanceof Error);
812+
assert.strictEqual(
813+
err.message,
814+
'Server indicated an extension that was not requested'
815+
);
816+
ws.on('close', () => done());
817+
});
818+
});
819+
696820
it('fails if server sends a subprotocol when none was requested', (done) => {
697821
const wss = new WebSocket.Server({ server });
698822

0 commit comments

Comments
 (0)