Fixed MeshRelay access control

This commit is contained in:
Ylian Saint-Hilaire 2018-10-16 10:52:05 -07:00
parent 700218c6bd
commit 2a7b0a4f2a
6 changed files with 103 additions and 56 deletions

View File

@ -26,6 +26,24 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
obj.domain = domain; obj.domain = domain;
if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); } if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); }
// Mesh Rights
const MESHRIGHT_EDITMESH = 1;
const MESHRIGHT_MANAGEUSERS = 2;
const MESHRIGHT_MANAGECOMPUTERS = 4;
const MESHRIGHT_REMOTECONTROL = 8;
const MESHRIGHT_AGENTCONSOLE = 16;
const MESHRIGHT_SERVERFILES = 32;
const MESHRIGHT_WAKEDEVICE = 64;
const MESHRIGHT_SETNOTES = 128;
// Site rights
const SITERIGHT_SERVERBACKUP = 1;
const SITERIGHT_MANAGEUSERS = 2;
const SITERIGHT_SERVERRESTORE = 4;
const SITERIGHT_FILEACCESS = 8;
const SITERIGHT_SERVERUPDATE = 16;
const SITERIGHT_LOCKED = 32;
// Disconnect this agent // Disconnect this agent
obj.close = function (arg) { obj.close = function (arg) {
if ((arg == 1) || (arg == null)) { try { obj.ws.close(); obj.parent.parent.debug(1, 'Relay: Soft disconnect (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket if ((arg == 1) || (arg == null)) { try { obj.ws.close(); obj.parent.parent.debug(1, 'Relay: Soft disconnect (' + obj.remoteaddr + ')'); } catch (e) { console.log(e); } } // Soft close, close the websocket
@ -71,25 +89,6 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
return false; return false;
}; };
// Mark this relay session as authenticated if this is the user end.
obj.authenticated = (obj.user != null);
// Kick off the routing, if we have agent routing instructions, process them here.
if ((obj.cookie != null) && (obj.cookie.nodeid != null) && (obj.cookie.tcpport != null) && (obj.cookie.domainid != null)) {
// We have routing instructions in the cookie, Send connection request to agent
if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
var command = { nodeid: obj.cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: obj.cookie.tcpport, tcpaddr: obj.cookie.tcpaddr };
obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
if (obj.sendAgentMessage(command, obj.cookie.userid, obj.cookie.domainid) == false) { obj.id = null; obj.parent.parent.debug(1, 'Relay: Unable to contact this agent (' + obj.remoteaddr + ')'); }
} else if ((req.query.nodeid != null) && (req.query.tcpport != null)) {
// We have routing instructions in the URL arguments, Send connection request to agent
if (obj.id == null) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
var command = { nodeid: req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: req.query.tcpport, tcpaddr: ((req.query.tcpaddr == null) ? '127.0.0.1' : req.query.tcpaddr) };
obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
if (obj.sendAgentMessage(command, userid, obj.domain.id) == false) { obj.id = null; obj.parent.parent.debug(1, 'Relay: Unable to contact this agent (' + obj.remoteaddr + ')'); }
}
performRelay();
function performRelay() { function performRelay() {
if (obj.id == null) { try { obj.close(); } catch (e) { } return null; } // Attempt to connect without id, drop this. if (obj.id == null) { try { obj.close(); } catch (e) { } return null; } // Attempt to connect without id, drop this.
ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive ws._socket.setKeepAlive(true, 240000); // Set TCP keep alive
@ -128,6 +127,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
obj.ws.send('c'); // Send connect to both peers obj.ws.send('c'); // Send connect to both peers
relayinfo.peer1.ws.send('c'); relayinfo.peer1.ws.send('c');
relayinfo.peer1.ws.resume(); // Release the traffic relayinfo.peer1.ws.resume(); // Release the traffic
relayinfo.peer2.ws.resume(); // Release the traffic
relayinfo.peer1.ws.peer = relayinfo.peer2.ws; relayinfo.peer1.ws.peer = relayinfo.peer2.ws;
relayinfo.peer2.ws.peer = relayinfo.peer1.ws; relayinfo.peer2.ws.peer = relayinfo.peer1.ws;
@ -198,5 +198,51 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
} }
}); });
// Mark this relay session as authenticated if this is the user end.
obj.authenticated = (obj.user != null);
if (obj.authenticated) {
// Kick off the routing, if we have agent routing instructions, process them here.
// Routing instructions can only be given by a authenticated user
if ((obj.cookie != null) && (obj.cookie.nodeid != null) && (obj.cookie.tcpport != null) && (obj.cookie.domainid != null)) {
// We have routing instructions in the cookie, but first, check user access for this node.
obj.parent.db.Get(obj.cookie.nodeid, function (err, docs) {
if (docs.length == 0) { console.log('ERR: Node not found'); try { obj.close(); } catch (e) { } return; } // Disconnect websocket
var node = docs[0];
// Check if this user has permission to manage this computer
var meshlinks = obj.user.links[node.meshid];
if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); try { obj.close(); } catch (e) { } return; }
// Send connection request to agent
if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
var command = { nodeid: obj.cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: obj.cookie.tcpport, tcpaddr: obj.cookie.tcpaddr };
obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
if (obj.sendAgentMessage(command, obj.user._id, obj.cookie.domainid) == false) { obj.id = null; obj.parent.parent.debug(1, 'Relay: Unable to contact this agent (' + obj.remoteaddr + ')'); }
performRelay();
});
return obj;
} else if ((req.query.nodeid != null) && (req.query.tcpport != null)) {
// We have routing instructions in the URL arguments, but first, check user access for this node.
obj.parent.db.Get(req.query.nodeid, function (err, docs) {
if (docs.length == 0) { console.log('ERR: Node not found'); try { obj.close(); } catch (e) { } return; } // Disconnect websocket
var node = docs[0];
// Check if this user has permission to manage this computer
var meshlinks = obj.user.links[node.meshid];
if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); try { obj.close(); } catch (e) { } return; }
// Send connection request to agent
if (obj.id == null) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
var command = { nodeid: req.query.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: req.query.tcpport, tcpaddr: ((req.query.tcpaddr == null) ? '127.0.0.1' : req.query.tcpaddr) };
obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
if (obj.sendAgentMessage(command, obj.user._id, obj.domain.id) == false) { obj.id = null; obj.parent.parent.debug(1, 'Relay: Unable to contact this agent (' + obj.remoteaddr + ')'); }
performRelay();
});
return obj;
}
}
// If this is not an authenticated session, or the session does not have routing instructions, just go ahead an connect to existing session.
performRelay();
return obj; return obj;
}; };

View File

@ -14,11 +14,12 @@
"use strict"; "use strict";
// Construct a MeshAgent object, called upon connection // Construct a MeshAgent object, called upon connection
module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, user) {
var obj = {}; var obj = {};
obj.db = db; obj.db = db;
obj.ws = ws; obj.ws = ws;
obj.args = args; obj.args = args;
obj.user = user;
obj.parent = parent; obj.parent = parent;
obj.domain = domain; obj.domain = domain;
obj.common = parent.common; obj.common = parent.common;
@ -77,27 +78,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
try { try {
// Check if the user is logged in // Check if the user is logged in
if ((!req.session) || (!req.session.userid) || (req.session.domainid != domain.id)) {
// If a default user is active, setup the session here.
if (obj.args.user && obj.parent.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) {
if (req.session && req.session.loginmode) { delete req.session.loginmode; }
req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase();
req.session.domainid = domain.id;
req.session.currentNode = '';
} else {
// Close the websocket connection
console.log('NOAUTH1');
ws.send(JSON.stringify({ action: 'close', cause: 'noauth' }));
try { obj.ws.close(); } catch (e) { }
return;
}
}
req.session.ws = obj.ws; // Associate this websocket session with the web session
req.session.ws.userid = req.session.userid;
req.session.ws.domainid = domain.id;
var user = obj.parent.users[req.session.userid];
if (user == null) { try { obj.ws.close(); } catch (e) { } return; } if (user == null) { try { obj.ws.close(); } catch (e) { } return; }
// Associate this websocket session with the web session
//req.session.ws = obj.ws;
//req.session.ws.userid = req.session.userid;
//req.session.ws.domainid = domain.id;
// Add this web socket session to session list // Add this web socket session to session list
obj.ws.sessionId = user._id + '/' + ('' + Math.random()).substring(2); obj.ws.sessionId = user._id + '/' + ('' + Math.random()).substring(2);
obj.parent.wssessions2[ws.sessionId] = obj.ws; obj.parent.wssessions2[ws.sessionId] = obj.ws;
@ -141,9 +128,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
// When data is received from the web socket // When data is received from the web socket
ws.on('message', function (msg) { ws.on('message', function (msg) {
var command, user = obj.parent.users[req.session.userid], i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0; var command, i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0;
try { command = JSON.parse(msg.toString('utf8')); } catch (e) { return; } try { command = JSON.parse(msg.toString('utf8')); } catch (e) { return; }
if ((user == null) || (obj.common.validateString(command.action, 3, 32) == false)) return; // User must be set and action must be a string between 3 and 32 chars if (obj.common.validateString(command.action, 3, 32) == false) return; // Action must be a string between 3 and 32 chars
switch (command.action) { switch (command.action) {
case 'ping': { try { ws.send(JSON.stringify({ action: 'pong' })); } catch (ex) { } break; } case 'ping': { try { ws.send(JSON.stringify({ action: 'pong' })); } catch (ex) { } break; }
@ -1344,10 +1331,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
try { ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: serverinfo })); } catch (ex) { } try { ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: serverinfo })); } catch (ex) { }
// Send user information to web socket, this is the first thing we send // Send user information to web socket, this is the first thing we send
var userinfo = obj.common.Clone(obj.parent.users[req.session.userid]); var userinfo = obj.common.Clone(obj.parent.users[user._id]);
delete userinfo.salt; delete userinfo.salt;
delete userinfo.hash; delete userinfo.hash;
try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: userinfo })); } catch (ex) { } try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: userinfo })); } catch (ex) { }
// We are all set, start receiving data
ws.resume();
} catch (e) { console.log(e); } } catch (e) { console.log(e); }
// Read entire file and return it in callback function // Read entire file and return it in callback function

View File

@ -1,6 +1,6 @@
{ {
"name": "meshcentral", "name": "meshcentral",
"version": "0.2.2-i", "version": "0.2.2-k",
"keywords": [ "keywords": [
"Remote Management", "Remote Management",
"Intel AMT", "Intel AMT",

View File

@ -263,7 +263,9 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
if (action == null) return; if (action == null) return;
if (!event) { var event = window.event; } if (!event) { var event = window.event; }
var kc = event.keyCode; var kc = event.keyCode;
if (kc == 0x3B) kc = 0xBA; // ';' key if (kc == 59) kc = 186; // Correct for ';' key in Firefox
if (kc == 61) kc = 187; // Correct for '=' key in Firefox
if (kc == 173) kc = 189; // Correct for '-' key in Firefox
obj.SendKeyMsgKC(action, kc); obj.SendKeyMsgKC(action, kc);
} }

View File

@ -1270,9 +1270,8 @@
} }
case 'getcookie': { case 'getcookie': {
if (message.tag == 'clickonce') { if (message.tag == 'clickonce') {
var basicPort = "{{{serverRedirPort}}}"==""?"{{{serverPublicPort}}}":"{{{serverRedirPort}}}"; var basicPort = "{{{serverRedirPort}}}" == "" ? "{{{serverPublicPort}}}" : "{{{serverRedirPort}}}";
//var rdpurl = "http://" + window.location.hostname + ":" + basicPort + "/clickonce/minirouter/MeshMiniRouter.application?WS=wss%3A%2F%2F" + window.location.hostname + "%2Fmeshrelay.ashx%3Fauth=" + message.cookie + "&CH={{{webcerthash}}}&AP=" + message.protocol + "&HOL=1"; var rdpurl = "http://" + window.location.hostname + ":" + basicPort + "/clickonce/minirouter/MeshMiniRouter.application?WS=wss%3A%2F%2F" + window.location.hostname + "%2Fmeshrelay.ashx%3Fauth=" + message.cookie + "&CH={{{webcerthash}}}&AP=" + message.protocol + ((debugmode == 1)?"":"&HOL=1");
var rdpurl = "http://" + window.location.hostname + ":" + basicPort + "/clickonce/minirouter/MeshMiniRouter.application?WS=wss%3A%2F%2F" + window.location.hostname + "%2Fmeshrelay.ashx%3Fauth=" + message.cookie + "&CH={{{webcerthash}}}&AP=" + message.protocol;
window.open(rdpurl, '_blank'); window.open(rdpurl, '_blank');
} }
break; break;

View File

@ -1063,7 +1063,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if (!(req.query.host)) { console.log('ERR: No host target specified'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket if (!(req.query.host)) { console.log('ERR: No host target specified'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
Debug(1, 'Websocket relay connected from ' + user.name + ' for ' + req.query.host + '.'); Debug(1, 'Websocket relay connected from ' + user.name + ' for ' + req.query.host + '.');
ws.pause(); // Hold this socket until we are ready.
try { ws._socket.setKeepAlive(true, 240000); } catch (ex) { } // Set TCP keep alive try { ws._socket.setKeepAlive(true, 240000); } catch (ex) { } // Set TCP keep alive
// Fetch information about the target // Fetch information about the target
@ -1104,6 +1103,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// If Intel AMT CIRA connection is available, use it // If Intel AMT CIRA connection is available, use it
if (((conn & 2) != 0) && (parent.mpsserver.ciraConnections[req.query.host] != null)) { if (((conn & 2) != 0) && (parent.mpsserver.ciraConnections[req.query.host] != null)) {
Debug(1, 'Opening relay CIRA channel connection to ' + req.query.host + '.');
var ciraconn = parent.mpsserver.ciraConnections[req.query.host]; var ciraconn = parent.mpsserver.ciraConnections[req.query.host];
// Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS // Compute target port, look at the CIRA port mappings, if non-TLS is allowed, use that, if not use TLS
@ -1114,7 +1115,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Setup a new CIRA channel // Setup a new CIRA channel
if ((port == 16993) || (port == 16995)) { if ((port == 16993) || (port == 16995)) {
// Perform TLS - ( TODO: THIS IS BROKEN on Intel AMT v7 but works on v10, Not sure why ) // Perform TLS - ( TODO: THIS IS BROKEN on Intel AMT v7 but works on v10, Not sure why. Well, could be broken TLS 1.0 in firmware )
var ser = new SerialTunnel(); var ser = new SerialTunnel();
var chnl = parent.mpsserver.SetupCiraChannel(ciraconn, port); var chnl = parent.mpsserver.SetupCiraChannel(ciraconn, port);
@ -1217,7 +1218,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// If Intel AMT direct connection is possible, option a direct socket // If Intel AMT direct connection is possible, option a direct socket
if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port. if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
Debug(2, 'Opening relay TCP socket connection to ' + req.query.host + '.'); Debug(1, 'Opening relay TCP socket connection to ' + req.query.host + '.');
// When data is received from the web socket, forward the data into the associated TCP connection. // When data is received from the web socket, forward the data into the associated TCP connection.
ws.on('message', function (msg) { ws.on('message', function (msg) {
@ -1795,7 +1796,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
obj.app.ws(url + 'meshrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { obj.meshRelayHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); }); }); obj.app.ws(url + 'meshrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { obj.meshRelayHandler.CreateMeshRelay(obj, ws1, req1, domain, user, cookie); }); });
obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); }); obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, handleRelayWebSocket); }); obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, handleRelayWebSocket); });
obj.app.ws(url + 'control.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, function (ws1, req1, domain, user, cookie) { obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain); }); }); obj.app.ws(url + 'control.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, function (ws1, req1, domain, user, cookie) { obj.meshUserHandler.CreateMeshUser(obj, obj.db, ws1, req1, obj.args, domain, user); }); });
// Server picture // Server picture
obj.app.get(url + 'serverpic.ashx', function (req, res) { obj.app.get(url + 'serverpic.ashx', function (req, res) {
@ -1831,6 +1832,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// Authenticates a session and forwards // Authenticates a session and forwards
function PerformWSSessionAuth(ws, req, noAuthOk, func) { function PerformWSSessionAuth(ws, req, noAuthOk, func) {
try { try {
// Hold this websocket until we are ready.
ws.pause();
// Check IP filtering and domain // Check IP filtering and domain
var domain = checkUserIpAddress(ws, req); var domain = checkUserIpAddress(ws, req);
if (domain == null) { try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); return; } catch (e) { return; } } if (domain == null) { try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); return; } catch (e) { return; } }
@ -1842,11 +1846,17 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
if ((err == null) && (obj.users[userid])) { if ((err == null) && (obj.users[userid])) {
// We are authenticated // We are authenticated
func(ws, req, domain, obj.users[userid]); func(ws, req, domain, obj.users[userid]);
} else {
// Failed to authenticate, see if a default user is active
if (obj.args.user && obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) {
// A default user is active
func(ws, req, domain, obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]);
} else { } else {
// If not authenticated, close the websocket connection // If not authenticated, close the websocket connection
Debug(1, 'ERR: Websocket bad user/pass auth'); Debug(1, 'ERR: Websocket bad user/pass auth');
try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); } catch (e) { } try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); } catch (e) { }
} }
}
}); });
return; return;
} else if (req.query.auth != null) { } else if (req.query.auth != null) {
@ -1865,9 +1875,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
// A default user is active // A default user is active
func(ws, req, domain, obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]); func(ws, req, domain, obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]);
return; return;
} else if (req.session && (req.session.userid != null) && (req.session.domainid == domain.id)) { } else if (req.session && (req.session.userid != null) && (req.session.domainid == domain.id) && (obj.users[req.session.userid])) {
// This user is logged in using the ExpressJS session // This user is logged in using the ExpressJS session
func(ws, req, domain, req.session.userid); func(ws, req, domain, obj.users[req.session.userid]);
return; return;
} }