From 2a7b0a4f2a4211df501a3e31c817b319a6cdb237 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 16 Oct 2018 10:52:05 -0700 Subject: [PATCH] Fixed MeshRelay access control --- meshrelay.js | 84 +++++++++++++++++++++------ meshuser.js | 36 +++++------- package.json | 2 +- public/scripts/agent-desktop-0.0.2.js | 4 +- views/default.handlebars | 5 +- webserver.js | 28 ++++++--- 6 files changed, 103 insertions(+), 56 deletions(-) diff --git a/meshrelay.js b/meshrelay.js index 88b65fc6..b47489de 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -26,6 +26,24 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie obj.domain = domain; 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 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 @@ -70,25 +88,6 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie } 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() { if (obj.id == null) { try { obj.close(); } catch (e) { } return null; } // Attempt to connect without id, drop this. @@ -128,6 +127,7 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie obj.ws.send('c'); // Send connect to both peers relayinfo.peer1.ws.send('c'); relayinfo.peer1.ws.resume(); // Release the traffic + relayinfo.peer2.ws.resume(); // Release the traffic relayinfo.peer1.ws.peer = relayinfo.peer2.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; }; diff --git a/meshuser.js b/meshuser.js index b5f611bd..2e9c49d2 100644 --- a/meshuser.js +++ b/meshuser.js @@ -14,11 +14,12 @@ "use strict"; // 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 = {}; obj.db = db; obj.ws = ws; obj.args = args; + obj.user = user; obj.parent = parent; obj.domain = domain; obj.common = parent.common; @@ -77,27 +78,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) { try { // 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; } + // 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 obj.ws.sessionId = user._id + '/' + ('' + Math.random()).substring(2); 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 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; } - 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) { 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) { } // 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.hash; 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); } // Read entire file and return it in callback function diff --git a/package.json b/package.json index 3207b3a4..d3fc5194 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.2-i", + "version": "0.2.2-k", "keywords": [ "Remote Management", "Intel AMT", diff --git a/public/scripts/agent-desktop-0.0.2.js b/public/scripts/agent-desktop-0.0.2.js index e2fce61b..afcf1868 100644 --- a/public/scripts/agent-desktop-0.0.2.js +++ b/public/scripts/agent-desktop-0.0.2.js @@ -263,7 +263,9 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) { if (action == null) return; if (!event) { var event = window.event; } 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); } diff --git a/views/default.handlebars b/views/default.handlebars index 9f002b53..a83b4772 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1270,9 +1270,8 @@ } case 'getcookie': { if (message.tag == 'clickonce') { - 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; + 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 + ((debugmode == 1)?"":"&HOL=1"); window.open(rdpurl, '_blank'); } break; diff --git a/webserver.js b/webserver.js index d9fdf54a..c7fbe152 100644 --- a/webserver.js +++ b/webserver.js @@ -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 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 // 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 (((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]; // 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 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 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 ((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. 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.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 + '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 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 function PerformWSSessionAuth(ws, req, noAuthOk, func) { try { + // Hold this websocket until we are ready. + ws.pause(); + // Check IP filtering and domain var domain = checkUserIpAddress(ws, req); if (domain == null) { try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); return; } catch (e) { return; } } @@ -1843,9 +1847,15 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // We are authenticated func(ws, req, domain, obj.users[userid]); } else { - // If not authenticated, close the websocket connection - Debug(1, 'ERR: Websocket bad user/pass auth'); - try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); } catch (e) { } + // 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 { + // If not authenticated, close the websocket connection + Debug(1, 'ERR: Websocket bad user/pass auth'); + try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); } catch (e) { } + } } }); return; @@ -1865,9 +1875,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // A default user is active func(ws, req, domain, obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]); 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 - func(ws, req, domain, req.session.userid); + func(ws, req, domain, obj.users[req.session.userid]); return; }