From a1690509470e9dd5559cec4e60908ca6c23e9ba0 Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 7 Jan 2021 10:51:55 +0100 Subject: [PATCH] revert: fix(security): do not allow all origins by default This reverts commit f78a575f66ab693c3ea96ea88429ddb1a44c86c7. This commit contains a breaking change which deviates from semver, which we try to follow as closely as possible. That's why this change is reverted and we will rather suggest users to upgrade to v3. Related: https://github.com/socketio/socket.io/discussions/3741 --- lib/index.js | 33 +++++++++++++++++++++++---------- test/socket.io.js | 37 ++++++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/lib/index.js b/lib/index.js index 3cae6c10de..5287e4ead0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -54,7 +54,7 @@ function Server(srv, opts){ this.parser = opts.parser || parser; this.encoder = new this.parser.Encoder(); this.adapter(opts.adapter || Adapter); - this.origins(opts.origins || []); + this.origins(opts.origins || '*:*'); this.sockets = this.of('/'); if (srv) this.attach(srv, opts); } @@ -67,18 +67,31 @@ function Server(srv, opts){ */ Server.prototype.checkRequest = function(req, fn) { - const origin = req.headers.origin; + var origin = req.headers.origin || req.headers.referer; - if (typeof this._origins === 'function') { - return this._origins(origin, fn); - } + // file:// URLs produce a null Origin which can't be authorized via echo-back + if ('null' == origin || null == origin) origin = '*'; + if (!!origin && typeof(this._origins) == 'function') return this._origins(origin, fn); + if (this._origins.indexOf('*:*') !== -1) return fn(null, true); if (origin) { - fn(null, this._origins.includes(origin)); - } else { - const noOriginIsValid = this._origins.length === 0; - fn(null, noOriginIsValid); + try { + var parts = url.parse(origin); + var defaultPort = 'https:' == parts.protocol ? 443 : 80; + parts.port = parts.port != null + ? parts.port + : defaultPort; + var ok = + ~this._origins.indexOf(parts.protocol + '//' + parts.hostname + ':' + parts.port) || + ~this._origins.indexOf(parts.hostname + ':' + parts.port) || + ~this._origins.indexOf(parts.hostname + ':*') || + ~this._origins.indexOf('*:' + parts.port); + debug('origin %s is %svalid', origin, !!ok ? '' : 'not '); + return fn(null, !!ok); + } catch (ex) { + } } + fn(null, false); }; /** @@ -224,7 +237,7 @@ Server.prototype.adapter = function(v){ Server.prototype.origins = function(v){ if (!arguments.length) return this._origins; - this._origins = typeof v === 'string' ? [v] : v; + this._origins = v; return this; }; diff --git a/test/socket.io.js b/test/socket.io.js index 7252bb78e7..6cc085b798 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -73,7 +73,7 @@ describe('socket.io', function(){ it('should be able to set origins to engine.io', function() { var srv = io(http()); srv.set('origins', 'http://hostname.com:*'); - expect(srv.origins()).to.eql(['http://hostname.com:*']); + expect(srv.origins()).to.be('http://hostname.com:*'); }); it('should be able to set authorization and send error packet', function(done) { @@ -262,6 +262,17 @@ describe('socket.io', function(){ }); }); + it('should allow request when origin defined an the same is specified', function(done) { + var sockets = io({ origins: 'http://foo.example:*' }).listen('54015'); + request.get('http://localhost:54015/socket.io/default/') + .set('origin', 'http://foo.example') + .query({ transport: 'polling' }) + .end(function (err, res) { + expect(res.status).to.be(200); + done(); + }); + }); + it('should allow request when origin defined as function and same is supplied', function(done) { var sockets = io({ origins: function(origin,callback){ if (origin == 'http://foo.example') { @@ -296,7 +307,7 @@ describe('socket.io', function(){ it('should allow request when origin defined as function and no origin is supplied', function(done) { var sockets = io({ origins: function(origin,callback){ - if (origin === undefined) { + if (origin == '*') { return callback(null, true); } return callback(null, false); @@ -309,6 +320,17 @@ describe('socket.io', function(){ }); }); + it('should default to port 443 when protocol is https', function(done) { + var sockets = io({ origins: 'https://foo.example:443' }).listen('54036'); + request.get('http://localhost:54036/socket.io/default/') + .set('origin', 'https://foo.example') + .query({ transport: 'polling' }) + .end(function (err, res) { + expect(res.status).to.be(200); + done(); + }); + }); + it('should allow request if custom function in opts.allowRequest returns true', function(done){ var sockets = io(http().listen(54022), { allowRequest: function (req, callback) { return callback(null, true); @@ -345,17 +367,6 @@ describe('socket.io', function(){ done(); }); }); - - it('should disallow any origin by default', (done) => { - io().listen('54025'); - request.get('http://localhost:54025/socket.io/default/') - .set('origin', 'https://foo.example') - .query({ transport: 'polling' }) - .end((err, res) => { - expect(res.status).to.be(403); - done(); - }); - }); }); describe('close', function(){