Skip to content

Added http referrer verification to manager.js verifyOrigin #466

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions lib/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ Manager.prototype.handshakeData = function (data) {
*/

Manager.prototype.verifyOrigin = function (request) {
var origin = request.headers.origin
var origin = request.headers.origin || request.headers.referer
, origins = this.get('origins');

if (origin === 'null') origin = '*';
Expand All @@ -878,14 +878,19 @@ Manager.prototype.verifyOrigin = function (request) {
if (origin) {
try {
var parts = url.parse(origin);

return
~origins.indexOf(parts.host + ':' + parts.port) ||
~origins.indexOf(parts.host + ':*') ||
var ok =
~origins.indexOf(parts.hostname + ':' + parts.port) ||
~origins.indexOf(parts.hostname + ':*') ||
~origins.indexOf('*:' + parts.port);
} catch (ex) {}
if (!ok) this.log.warn('illegal origin: ' + origin);
return ok;
} catch (ex) {
this.log.warn('error parsing origin');
}
}
else {
this.log.warn('origin missing from handshake, yet required by config');
}

return false;
};

Expand Down
2 changes: 1 addition & 1 deletion lib/transports/flashsocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ exports = module.exports = FlashSocket;
*/

function FlashSocket (mng, data, req) {
WebSocket.call(this, mng, data, req);
return WebSocket.call(this, mng, data, req);
}

/**
Expand Down
327 changes: 5 additions & 322 deletions lib/transports/websocket.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@
* Module requirements.
*/

var Transport = require('../transport')
, EventEmitter = process.EventEmitter
, crypto = require('crypto')
, parser = require('../parser');
var protocolVersions = require('./wsver');

/**
* Export the constructor.
Expand All @@ -28,323 +25,9 @@ exports = module.exports = WebSocket;
*/

function WebSocket (mng, data, req) {
// parser
var self = this;

this.parser = new Parser();
this.parser.on('data', function (packet) {
self.log.debug(self.name + ' received data packet', packet);
self.onMessage(parser.decodePacket(packet));
});
this.parser.on('close', function () {
self.end();
});
this.parser.on('error', function () {
self.end();
});

Transport.call(this, mng, data, req);
};

/**
* Inherits from Transport.
*/

WebSocket.prototype.__proto__ = Transport.prototype;

/**
* Transport name
*
* @api public
*/

WebSocket.prototype.name = 'websocket';

/**
* Called when the socket connects.
*
* @api private
*/

WebSocket.prototype.onSocketConnect = function () {
var self = this;

this.socket.setNoDelay(true);

this.buffer = true;
this.buffered = [];

if (this.req.headers.upgrade !== 'WebSocket') {
this.log.warn(this.name + ' connection invalid');
this.end();
return;
var version = req.headers['sec-websocket-version'];
if (typeof version !== 'undefined' && typeof protocolVersions[version] !== 'undefined') {
return new protocolVersions[version](mng, data, req);
}

var origin = this.req.headers.origin
, location = (this.socket.encrypted ? 'wss' : 'ws')
+ '://' + this.req.headers.host + this.req.url
, waitingForNonce = false;

if (this.req.headers['sec-websocket-key1']) {
// If we don't have the nonce yet, wait for it (HAProxy compatibility).
if (! (this.req.head && this.req.head.length >= 8)) {
waitingForNonce = true;
}

var headers = [
'HTTP/1.1 101 WebSocket Protocol Handshake'
, 'Upgrade: WebSocket'
, 'Connection: Upgrade'
, 'Sec-WebSocket-Origin: ' + origin
, 'Sec-WebSocket-Location: ' + location
];

if (this.req.headers['sec-websocket-protocol']){
headers.push('Sec-WebSocket-Protocol: '
+ this.req.headers['sec-websocket-protocol']);
}
} else {
var headers = [
'HTTP/1.1 101 Web Socket Protocol Handshake'
, 'Upgrade: WebSocket'
, 'Connection: Upgrade'
, 'WebSocket-Origin: ' + origin
, 'WebSocket-Location: ' + location
];
}

try {
this.socket.write(headers.concat('', '').join('\r\n'));
this.socket.setTimeout(0);
this.socket.setNoDelay(true);
this.socket.setEncoding('utf8');
} catch (e) {
this.end();
return;
}

if (waitingForNonce) {
this.socket.setEncoding('binary');
} else if (this.proveReception(headers)) {
self.flush();
}

var headBuffer = '';

this.socket.on('data', function (data) {
if (waitingForNonce) {
headBuffer += data;

if (headBuffer.length < 8) {
return;
}

// Restore the connection to utf8 encoding after receiving the nonce
self.socket.setEncoding('utf8');
waitingForNonce = false;

// Stuff the nonce into the location where it's expected to be
self.req.head = headBuffer.substr(0, 8);
headBuffer = '';

if (self.proveReception(headers)) {
self.flush();
}

return;
}

self.parser.add(data);
});
};

/**
* Writes to the socket.
*
* @api private
*/

WebSocket.prototype.write = function (data) {
if (this.open) {
this.drained = false;

if (this.buffer) {
this.buffered.push(data);
return this;
}

var length = Buffer.byteLength(data)
, buffer = new Buffer(2 + length);

buffer.write('\u0000', 'binary');
buffer.write(data, 1, 'utf8');
buffer.write('\uffff', 1 + length, 'binary');

try {
if (this.socket.write(buffer)) {
this.drained = true;
}
} catch (e) {
this.end();
}

this.log.debug(this.name + ' writing', data);
}
};

/**
* Flushes the internal buffer
*
* @api private
*/

WebSocket.prototype.flush = function () {
this.buffer = false;

for (var i = 0, l = this.buffered.length; i < l; i++) {
this.write(this.buffered.splice(0, 1)[0]);
}
};

/**
* Finishes the handshake.
*
* @api private
*/

WebSocket.prototype.proveReception = function (headers) {
var self = this
, k1 = this.req.headers['sec-websocket-key1']
, k2 = this.req.headers['sec-websocket-key2'];

if (k1 && k2){
var md5 = crypto.createHash('md5');

[k1, k2].forEach(function (k) {
var n = parseInt(k.replace(/[^\d]/g, ''))
, spaces = k.replace(/[^ ]/g, '').length;

if (spaces === 0 || n % spaces !== 0){
self.log.warn('Invalid ' + self.name + ' key: "' + k + '".');
self.end();
return false;
}

n /= spaces;

md5.update(String.fromCharCode(
n >> 24 & 0xFF,
n >> 16 & 0xFF,
n >> 8 & 0xFF,
n & 0xFF));
});

md5.update(this.req.head.toString('binary'));

try {
this.socket.write(md5.digest('binary'), 'binary');
} catch (e) {
this.end();
}
}

return true;
};

/**
* Writes a payload.
*
* @api private
*/

WebSocket.prototype.payload = function (msgs) {
for (var i = 0, l = msgs.length; i < l; i++) {
this.write(msgs[i]);
}

return this;
};

/**
* Closes the connection.
*
* @api private
*/

WebSocket.prototype.doClose = function () {
this.socket.end();
};

/**
* WebSocket parser
*
* @api public
*/

function Parser () {
this.buffer = '';
this.i = 0;
};

/**
* Inherits from EventEmitter.
*/

Parser.prototype.__proto__ = EventEmitter.prototype;

/**
* Adds data to the buffer.
*
* @api public
*/

Parser.prototype.add = function (data) {
this.buffer += data;
this.parse();
};

/**
* Parses the buffer.
*
* @api private
*/

Parser.prototype.parse = function () {
for (var i = this.i, chr, l = this.buffer.length; i < l; i++){
chr = this.buffer[i];

if (this.buffer.length == 2 && this.buffer[1] == '\u0000') {
this.emit('close');
this.buffer = '';
this.i = 0;
return;
}

if (i === 0){
if (chr != '\u0000')
this.error('Bad framing. Expected null byte as first frame');
else
continue;
}

if (chr == '\ufffd'){
this.emit('data', this.buffer.substr(1, i - 1));
this.buffer = this.buffer.substr(i + 1);
this.i = 0;
return this.parse();
}
}
};

/**
* Handles an error
*
* @api private
*/

Parser.prototype.error = function (reason) {
this.buffer = '';
this.i = 0;
this.emit('error', reason);
return this;
return new protocolVersions['default'](mng, data, req);
};
Loading