Skip to content

Commit c7a2901

Browse files
authored
fix: reject h2 websocket upgrades on non-200 responses (#5072)
1 parent f6c5dda commit c7a2901

File tree

2 files changed

+55
-3
lines changed

2 files changed

+55
-3
lines changed

lib/api/api-upgrade.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,13 @@ class UpgradeHandler extends AsyncResource {
5151
}
5252

5353
onRequestUpgrade (controller, statusCode, headers, socket) {
54-
assert(socket[kHTTP2Stream] === true ? statusCode === 200 : statusCode === 101)
54+
const expectedStatusCode = socket[kHTTP2Stream] === true ? 200 : 101
55+
56+
if (statusCode !== expectedStatusCode) {
57+
const socketInfo = socket[kHTTP2Stream] === true ? null : util.getSocketInfo(socket)
58+
controller.abort(new SocketError('bad upgrade', socketInfo))
59+
return
60+
}
5561

5662
const { callback, opaque, context } = this
5763

test/http2-dispatcher.js

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const { tspl } = require('@matteo.collina/tspl')
1010

1111
const pem = require('@metcoder95/https-pem')
1212

13-
const { Client } = require('..')
13+
const { Client, errors } = require('..')
1414

1515
test('Dispatcher#Stream', async t => {
1616
t = tspl(t, { plan: 4 })
@@ -254,7 +254,10 @@ test('Dispatcher#Upgrade - Should throw on non-websocket upgrade', async t => {
254254
test('Dispatcher#Upgrade', async t => {
255255
t = tspl(t, { plan: 3 })
256256

257-
const server = createSecureServer({ ...(await pem.generate({ opts: { keySize: 2048 } })), settings: { enableConnectProtocol: true } })
257+
const server = createSecureServer({
258+
...(await pem.generate({ opts: { keySize: 2048 } })),
259+
settings: { enableConnectProtocol: true }
260+
})
258261

259262
server.on('stream', (stream, headers) => {
260263
stream.on('error', err => {
@@ -288,6 +291,49 @@ test('Dispatcher#Upgrade', async t => {
288291
await t.completed
289292
})
290293

294+
test('Dispatcher#Upgrade rejects websocket upgrade on non-200 HTTP/2 response', async t => {
295+
t = tspl(t, { plan: 3 })
296+
297+
const server = createSecureServer({
298+
...(await pem.generate({ opts: { keySize: 2048 } })),
299+
settings: { enableConnectProtocol: true }
300+
})
301+
302+
server.on('stream', (stream, headers) => {
303+
stream.on('error', () => {})
304+
305+
if (headers[':method'] === 'CONNECT' && headers[':protocol'] === 'websocket') {
306+
stream.respond({ ':status': 404 })
307+
stream.end('not found')
308+
return
309+
}
310+
311+
stream.respond({ ':status': 200 })
312+
stream.end()
313+
})
314+
315+
await once(server.listen(0), 'listening')
316+
317+
const client = new Client(`https://localhost:${server.address().port}`, {
318+
connect: {
319+
rejectUnauthorized: false
320+
},
321+
allowH2: true
322+
})
323+
after(() => client.close().then(() => { server.close() }))
324+
325+
try {
326+
await client.upgrade({ path: '/', protocol: 'websocket' })
327+
t.fail('client.upgrade() should reject')
328+
} catch (err) {
329+
t.ok(err instanceof errors.SocketError)
330+
t.strictEqual(err.code, 'UND_ERR_SOCKET')
331+
t.strictEqual(err.message, 'bad upgrade')
332+
}
333+
334+
await t.completed
335+
})
336+
291337
test('Dispatcher#destroy', async t => {
292338
t = tspl(t, { plan: 4 })
293339

0 commit comments

Comments
 (0)