From 56a92c25d5026c2cc87f7bf405c4869a1498e4bc Mon Sep 17 00:00:00 2001 From: Tim Masliuchenko Date: Mon, 14 Jan 2019 16:14:53 +0200 Subject: [PATCH 1/3] Check origin header for websocket connection --- lib/Server.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/Server.js b/lib/Server.js index 3250722f5f..6f28bbc03d 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -510,13 +510,14 @@ Server.prototype.setContentHeaders = function (req, res, next) { next(); }; -Server.prototype.checkHost = function (headers) { +Server.prototype.checkHost = function (headers, headerToCheck) { // allow user to opt-out this security check, at own risk if (this.disableHostCheck) return true; + if (!headerToCheck) headerToCheck = 'host'; // get the Host header and extract hostname // we don't care about port not matching - const hostHeader = headers.host; + const hostHeader = headers[headerToCheck]; if (!hostHeader) return false; // use the node url-parser to retrieve the hostname from the host-header. @@ -581,8 +582,8 @@ Server.prototype.listen = function (port, hostname, fn) { sockServer.on('connection', (conn) => { if (!conn) return; - if (!this.checkHost(conn.headers)) { - this.sockWrite([conn], 'error', 'Invalid Host header'); + if (!this.checkHost(conn.headers) || !this.checkHost(conn.headers, 'origin')) { + this.sockWrite([conn], 'error', 'Invalid Host/Origin header'); conn.close(); return; } From f6c6af63a2bf426eb47b5afd4f25630d5ed886bf Mon Sep 17 00:00:00 2001 From: Tim Masliuchenko Date: Wed, 13 Mar 2019 16:38:48 +0200 Subject: [PATCH 2/3] fix: regression in for checking Origin header --- lib/Server.js | 7 ++++++- test/Validation.test.js | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/Server.js b/lib/Server.js index 6f28bbc03d..b2fb6ec7be 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -521,7 +521,12 @@ Server.prototype.checkHost = function (headers, headerToCheck) { if (!hostHeader) return false; // use the node url-parser to retrieve the hostname from the host-header. - const hostname = url.parse(`//${hostHeader}`, false, true).hostname; + const hostname = url.parse( + // if hostHeader doesn't have scheme, add // for parsing. + /^(.+:)?\/\//.test(hostHeader) ? hostHeader : `//${hostHeader}`, + false, + true, + ).hostname; // always allow requests with explicit IPv4 or IPv6-address. // A note on IPv6 addresses: hostHeader will always contain the brackets denoting diff --git a/test/Validation.test.js b/test/Validation.test.js index b4284a5e59..ac0f520afd 100644 --- a/test/Validation.test.js +++ b/test/Validation.test.js @@ -144,6 +144,19 @@ describe('Validation', () => { } }); + it('should allow urls with scheme for checking origin', () => { + const options = { + public: 'test.host:80' + }; + const headers = { + origin: 'https://test.host' + }; + const server = new Server(compiler, options); + if (!server.checkHost(headers, 'origin')) { + throw new Error("Validation didn't fail"); + } + }); + describe('allowedHosts', () => { it('should allow hosts in allowedHosts', () => { const testHosts = [ From ff8b19c5814b86dbb0ac4b485bef016bef3f8a49 Mon Sep 17 00:00:00 2001 From: Tim Masliuchenko Date: Wed, 13 Mar 2019 16:41:25 +0200 Subject: [PATCH 3/3] fix: add workaround for Origin header in sockjs --- lib/Server.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/Server.js b/lib/Server.js index b2fb6ec7be..4bf57bc5c4 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -24,6 +24,22 @@ const webpackDevMiddleware = require('webpack-dev-middleware'); const OptionsValidationError = require('./OptionsValidationError'); const optionsSchema = require('./optionsSchema.json'); +// Workaround for sockjs@~0.3.19 +// sockjs will remove Origin header, however Origin header is required for checking host. +// See https://github.com/webpack/webpack-dev-server/issues/1604 for more information +{ + // eslint-disable-next-line global-require + const SockjsSession = require('sockjs/lib/transport').Session; + const decorateConnection = SockjsSession.prototype.decorateConnection; + SockjsSession.prototype.decorateConnection = function(req) { + decorateConnection.call(this, req); + const connection = this.connection; + if (connection.headers && !('origin' in connection.headers) && 'origin' in req.headers) { + connection.headers.origin = req.headers.origin; + } + }; +} + const clientStats = { errorDetails: false }; const log = console.log; // eslint-disable-line no-console