Skip to content

Commit e55e510

Browse files
committed
[security] Fix crash when the Upgrade header cannot be read (#2231)
It is possible that the Upgrade header is correctly received and handled (the `'upgrade'` event is emitted) without its value being returned to the user. This can happen if the number of received headers exceed the `server.maxHeadersCount` or `request.maxHeadersCount` threshold. In this case `incomingMessage.headers.upgrade` may not be set. Handle the case correctly and abort the handshake. Fixes #2230
1 parent 6a00029 commit e55e510

File tree

4 files changed

+76
-3
lines changed

4 files changed

+76
-3
lines changed

lib/websocket-server.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ class WebSocketServer extends EventEmitter {
235235
socket.on('error', socketOnError);
236236

237237
const key = req.headers['sec-websocket-key'];
238+
const upgrade = req.headers.upgrade;
238239
const version = +req.headers['sec-websocket-version'];
239240

240241
if (req.method !== 'GET') {
@@ -243,13 +244,13 @@ class WebSocketServer extends EventEmitter {
243244
return;
244245
}
245246

246-
if (req.headers.upgrade.toLowerCase() !== 'websocket') {
247+
if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') {
247248
const message = 'Invalid Upgrade header';
248249
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
249250
return;
250251
}
251252

252-
if (!key || !keyRegex.test(key)) {
253+
if (key === undefined || !keyRegex.test(key)) {
253254
const message = 'Missing or invalid Sec-WebSocket-Key header';
254255
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
255256
return;

lib/websocket.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,9 @@ function initAsClient(websocket, address, protocols, options) {
928928

929929
req = websocket._req = null;
930930

931-
if (res.headers.upgrade.toLowerCase() !== 'websocket') {
931+
const upgrade = res.headers.upgrade;
932+
933+
if (upgrade === undefined || upgrade.toLowerCase() !== 'websocket') {
932934
abortHandshake(websocket, socket, 'Invalid Upgrade header');
933935
return;
934936
}

test/websocket-server.test.js

+44
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,50 @@ describe('WebSocketServer', () => {
653653
});
654654
});
655655

656+
it('fails if the Upgrade header field value cannot be read', (done) => {
657+
const server = http.createServer();
658+
const wss = new WebSocket.Server({ noServer: true });
659+
660+
server.maxHeadersCount = 1;
661+
662+
server.on('upgrade', (req, socket, head) => {
663+
assert.deepStrictEqual(req.headers, { foo: 'bar' });
664+
wss.handleUpgrade(req, socket, head, () => {
665+
done(new Error('Unexpected callback invocation'));
666+
});
667+
});
668+
669+
server.listen(() => {
670+
const req = http.get({
671+
port: server.address().port,
672+
headers: {
673+
foo: 'bar',
674+
bar: 'baz',
675+
Connection: 'Upgrade',
676+
Upgrade: 'websocket'
677+
}
678+
});
679+
680+
req.on('response', (res) => {
681+
assert.strictEqual(res.statusCode, 400);
682+
683+
const chunks = [];
684+
685+
res.on('data', (chunk) => {
686+
chunks.push(chunk);
687+
});
688+
689+
res.on('end', () => {
690+
assert.strictEqual(
691+
Buffer.concat(chunks).toString(),
692+
'Invalid Upgrade header'
693+
);
694+
server.close(done);
695+
});
696+
});
697+
});
698+
});
699+
656700
it('fails if the Upgrade header field value is not "websocket"', (done) => {
657701
const wss = new WebSocket.Server({ port: 0 }, () => {
658702
const req = http.get({

test/websocket.test.js

+26
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,32 @@ describe('WebSocket', () => {
757757
beforeEach((done) => server.listen(0, done));
758758
afterEach((done) => server.close(done));
759759

760+
it('fails if the Upgrade header field value cannot be read', (done) => {
761+
server.once('upgrade', (req, socket) => {
762+
socket.on('end', socket.end);
763+
socket.write(
764+
'HTTP/1.1 101 Switching Protocols\r\n' +
765+
'Connection: Upgrade\r\n' +
766+
'Upgrade: websocket\r\n' +
767+
'\r\n'
768+
);
769+
});
770+
771+
const ws = new WebSocket(`ws://localhost:${server.address().port}`);
772+
773+
ws._req.maxHeadersCount = 1;
774+
775+
ws.on('upgrade', (res) => {
776+
assert.deepStrictEqual(res.headers, { connection: 'Upgrade' });
777+
778+
ws.on('error', (err) => {
779+
assert.ok(err instanceof Error);
780+
assert.strictEqual(err.message, 'Invalid Upgrade header');
781+
done();
782+
});
783+
});
784+
});
785+
760786
it('fails if the Upgrade header field value is not "websocket"', (done) => {
761787
server.once('upgrade', (req, socket) => {
762788
socket.on('end', socket.end);

0 commit comments

Comments
 (0)