Fixed MeshRelay access control

Ylian Saint-Hilaire committed Oct 16, 2018 at 10:52 UTC 2a7b0a4f2a4211df501a3e31c817b319a6cdb237
6 files changed +103 -56
meshrelay.js
+65 -19
@@ -26,6 +26,24 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
26 obj.domain = domain;
27 if (obj.remoteaddr.startsWith('::ffff:')) { obj.remoteaddr = obj.remoteaddr.substring(7); }
28
29 + // Mesh Rights
30 + const MESHRIGHT_EDITMESH = 1;
31 + const MESHRIGHT_MANAGEUSERS = 2;
32 + const MESHRIGHT_MANAGECOMPUTERS = 4;
33 + const MESHRIGHT_REMOTECONTROL = 8;
34 + const MESHRIGHT_AGENTCONSOLE = 16;
35 + const MESHRIGHT_SERVERFILES = 32;
36 + const MESHRIGHT_WAKEDEVICE = 64;
37 + const MESHRIGHT_SETNOTES = 128;
38 +
39 + // Site rights
40 + const SITERIGHT_SERVERBACKUP = 1;
41 + const SITERIGHT_MANAGEUSERS = 2;
42 + const SITERIGHT_SERVERRESTORE = 4;
43 + const SITERIGHT_FILEACCESS = 8;
44 + const SITERIGHT_SERVERUPDATE = 16;
45 + const SITERIGHT_LOCKED = 32;
46 +
47 // Disconnect this agent
48 obj.close = function (arg) {
49 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
88 }
89 return false;
90 };
73 -
74 - // Mark this relay session as authenticated if this is the user end.
75 - obj.authenticated = (obj.user != null);
76 -
77 - // Kick off the routing, if we have agent routing instructions, process them here.
78 - if ((obj.cookie != null) && (obj.cookie.nodeid != null) && (obj.cookie.tcpport != null) && (obj.cookie.domainid != null)) {
79 - // We have routing instructions in the cookie, Send connection request to agent
80 - if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
81 - var command = { nodeid: obj.cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: obj.cookie.tcpport, tcpaddr: obj.cookie.tcpaddr };
82 - obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
83 - 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 + ')'); }
84 - } else if ((req.query.nodeid != null) && (req.query.tcpport != null)) {
85 - // We have routing instructions in the URL arguments, Send connection request to agent
86 - if (obj.id == null) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
87 - 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) };
88 - obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
89 - 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 + ')'); }
90 - }
91 - performRelay();
91
92 function performRelay() {
93 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
127 obj.ws.send('c'); // Send connect to both peers
128 relayinfo.peer1.ws.send('c');
129 relayinfo.peer1.ws.resume(); // Release the traffic
130 + relayinfo.peer2.ws.resume(); // Release the traffic
131
132 relayinfo.peer1.ws.peer = relayinfo.peer2.ws;
133 relayinfo.peer2.ws.peer = relayinfo.peer1.ws;
@@ -198,5 +198,51 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie
198 }
199 });
200
201 + // Mark this relay session as authenticated if this is the user end.
202 + obj.authenticated = (obj.user != null);
203 + if (obj.authenticated) {
204 + // Kick off the routing, if we have agent routing instructions, process them here.
205 + // Routing instructions can only be given by a authenticated user
206 + if ((obj.cookie != null) && (obj.cookie.nodeid != null) && (obj.cookie.tcpport != null) && (obj.cookie.domainid != null)) {
207 + // We have routing instructions in the cookie, but first, check user access for this node.
208 + obj.parent.db.Get(obj.cookie.nodeid, function (err, docs) {
209 + if (docs.length == 0) { console.log('ERR: Node not found'); try { obj.close(); } catch (e) { } return; } // Disconnect websocket
210 + var node = docs[0];
211 +
212 + // Check if this user has permission to manage this computer
213 + var meshlinks = obj.user.links[node.meshid];
214 + if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); try { obj.close(); } catch (e) { } return; }
215 +
216 + // Send connection request to agent
217 + if (obj.id == undefined) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
218 + var command = { nodeid: obj.cookie.nodeid, action: 'msg', type: 'tunnel', value: '*/meshrelay.ashx?id=' + obj.id, tcpport: obj.cookie.tcpport, tcpaddr: obj.cookie.tcpaddr };
219 + obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
220 + 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 + ')'); }
221 + performRelay();
222 + });
223 + return obj;
224 + } else if ((req.query.nodeid != null) && (req.query.tcpport != null)) {
225 + // We have routing instructions in the URL arguments, but first, check user access for this node.
226 + obj.parent.db.Get(req.query.nodeid, function (err, docs) {
227 + if (docs.length == 0) { console.log('ERR: Node not found'); try { obj.close(); } catch (e) { } return; } // Disconnect websocket
228 + var node = docs[0];
229 +
230 + // Check if this user has permission to manage this computer
231 + var meshlinks = obj.user.links[node.meshid];
232 + if ((!meshlinks) || (!meshlinks.rights) || ((meshlinks.rights & MESHRIGHT_REMOTECONTROL) == 0)) { console.log('ERR: Access denied (2)'); try { obj.close(); } catch (e) { } return; }
233 +
234 + // Send connection request to agent
235 + if (obj.id == null) { obj.id = ('' + Math.random()).substring(2); } // If there is no connection id, generate one.
236 + 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) };
237 + obj.parent.parent.debug(1, 'Relay: Sending agent tunnel command: ' + JSON.stringify(command));
238 + 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 + ')'); }
239 + performRelay();
240 + });
241 + return obj;
242 + }
243 + }
244 +
245 + // If this is not an authenticated session, or the session does not have routing instructions, just go ahead an connect to existing session.
246 + performRelay();
247 return obj;
248 };
meshuser.js
+13 -23
@@ -14,11 +14,12 @@
14 "use strict";
15
16 // Construct a MeshAgent object, called upon connection
17 -module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
17 +module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, user) {
18 var obj = {};
19 obj.db = db;
20 obj.ws = ws;
21 obj.args = args;
22 + obj.user = user;
23 obj.parent = parent;
24 obj.domain = domain;
25 obj.common = parent.common;
@@ -77,27 +78,13 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
78
79 try {
80 // Check if the user is logged in
80 - if ((!req.session) || (!req.session.userid) || (req.session.domainid != domain.id)) {
81 - // If a default user is active, setup the session here.
82 - if (obj.args.user && obj.parent.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) {
83 - if (req.session && req.session.loginmode) { delete req.session.loginmode; }
84 - req.session.userid = 'user/' + domain.id + '/' + obj.args.user.toLowerCase();
85 - req.session.domainid = domain.id;
86 - req.session.currentNode = '';
87 - } else {
88 - // Close the websocket connection
89 - console.log('NOAUTH1');
90 - ws.send(JSON.stringify({ action: 'close', cause: 'noauth' }));
91 - try { obj.ws.close(); } catch (e) { }
92 - return;
93 - }
94 - }
95 - req.session.ws = obj.ws; // Associate this websocket session with the web session
96 - req.session.ws.userid = req.session.userid;
97 - req.session.ws.domainid = domain.id;
98 - var user = obj.parent.users[req.session.userid];
81 if (user == null) { try { obj.ws.close(); } catch (e) { } return; }
82
83 + // Associate this websocket session with the web session
84 + //req.session.ws = obj.ws;
85 + //req.session.ws.userid = req.session.userid;
86 + //req.session.ws.domainid = domain.id;
87 +
88 // Add this web socket session to session list
89 obj.ws.sessionId = user._id + '/' + ('' + Math.random()).substring(2);
90 obj.parent.wssessions2[ws.sessionId] = obj.ws;
@@ -141,9 +128,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain) {
128
129 // When data is received from the web socket
130 ws.on('message', function (msg) {
144 - var command, user = obj.parent.users[req.session.userid], i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0;
131 + var command, i = 0, mesh = null, meshid = null, nodeid = null, meshlinks = null, change = 0;
132 try { command = JSON.parse(msg.toString('utf8')); } catch (e) { return; }
146 - 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
133 + if (obj.common.validateString(command.action, 3, 32) == false) return; // Action must be a string between 3 and 32 chars
134
135 switch (command.action) {
136 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) {
1331 try { ws.send(JSON.stringify({ action: 'serverinfo', serverinfo: serverinfo })); } catch (ex) { }
1332
1333 // Send user information to web socket, this is the first thing we send
1347 - var userinfo = obj.common.Clone(obj.parent.users[req.session.userid]);
1334 + var userinfo = obj.common.Clone(obj.parent.users[user._id]);
1335 delete userinfo.salt;
1336 delete userinfo.hash;
1337 try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: userinfo })); } catch (ex) { }
1338 +
1339 + // We are all set, start receiving data
1340 + ws.resume();
1341 } catch (e) { console.log(e); }
1342
1343 // Read entire file and return it in callback function
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.2.2-i",
3 + "version": "0.2.2-k",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
public/scripts/agent-desktop-0.0.2.js
+3 -1
@@ -263,7 +263,9 @@ var CreateAgentRemoteDesktop = function (canvasid, scrolldiv) {
263 if (action == null) return;
264 if (!event) { var event = window.event; }
265 var kc = event.keyCode;
266 - if (kc == 0x3B) kc = 0xBA; // ';' key
266 + if (kc == 59) kc = 186; // Correct for ';' key in Firefox
267 + if (kc == 61) kc = 187; // Correct for '=' key in Firefox
268 + if (kc == 173) kc = 189; // Correct for '-' key in Firefox
269 obj.SendKeyMsgKC(action, kc);
270 }
271
views/default.handlebars
+2 -3
@@ -1270,9 +1270,8 @@
1270 }
1271 case 'getcookie': {
1272 if (message.tag == 'clickonce') {
1273 - var basicPort = "{{{serverRedirPort}}}"==""?"{{{serverPublicPort}}}":"{{{serverRedirPort}}}";
1274 - //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";
1275 - 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;
1273 + var basicPort = "{{{serverRedirPort}}}" == "" ? "{{{serverPublicPort}}}" : "{{{serverRedirPort}}}";
1274 + 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");
1275 window.open(rdpurl, '_blank');
1276 }
1277 break;
webserver.js
+19 -9
@@ -1063,7 +1063,6 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1063 if (!(req.query.host)) { console.log('ERR: No host target specified'); try { ws.close(); } catch (e) { } return; } // Disconnect websocket
1064 Debug(1, 'Websocket relay connected from ' + user.name + ' for ' + req.query.host + '.');
1065
1066 - ws.pause(); // Hold this socket until we are ready.
1066 try { ws._socket.setKeepAlive(true, 240000); } catch (ex) { } // Set TCP keep alive
1067
1068 // Fetch information about the target
@@ -1104,6 +1103,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1103
1104 // If Intel AMT CIRA connection is available, use it
1105 if (((conn & 2) != 0) && (parent.mpsserver.ciraConnections[req.query.host] != null)) {
1106 + Debug(1, 'Opening relay CIRA channel connection to ' + req.query.host + '.');
1107 +
1108 var ciraconn = parent.mpsserver.ciraConnections[req.query.host];
1109
1110 // 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) {
1115
1116 // Setup a new CIRA channel
1117 if ((port == 16993) || (port == 16995)) {
1117 - // Perform TLS - ( TODO: THIS IS BROKEN on Intel AMT v7 but works on v10, Not sure why )
1118 + // 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 )
1119 var ser = new SerialTunnel();
1120 var chnl = parent.mpsserver.SetupCiraChannel(ciraconn, port);
1121
@@ -1217,7 +1218,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1218
1219 // If Intel AMT direct connection is possible, option a direct socket
1220 if ((conn & 4) != 0) { // We got a new web socket connection, initiate a TCP connection to the target Intel AMT host/port.
1220 - Debug(2, 'Opening relay TCP socket connection to ' + req.query.host + '.');
1221 + Debug(1, 'Opening relay TCP socket connection to ' + req.query.host + '.');
1222
1223 // When data is received from the web socket, forward the data into the associated TCP connection.
1224 ws.on('message', function (msg) {
@@ -1795,7 +1796,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1796 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); }); });
1797 obj.app.get(url + 'webrelay.ashx', function (req, res) { res.send('Websocket connection expected'); });
1798 obj.app.ws(url + 'webrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, false, handleRelayWebSocket); });
1798 - 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); }); });
1799 + 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); }); });
1800
1801 // Server picture
1802 obj.app.get(url + 'serverpic.ashx', function (req, res) {
@@ -1831,6 +1832,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1832 // Authenticates a session and forwards
1833 function PerformWSSessionAuth(ws, req, noAuthOk, func) {
1834 try {
1835 + // Hold this websocket until we are ready.
1836 + ws.pause();
1837 +
1838 // Check IP filtering and domain
1839 var domain = checkUserIpAddress(ws, req);
1840 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) {
1847 // We are authenticated
1848 func(ws, req, domain, obj.users[userid]);
1849 } else {
1846 - // If not authenticated, close the websocket connection
1847 - Debug(1, 'ERR: Websocket bad user/pass auth');
1848 - try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); } catch (e) { }
1850 + // Failed to authenticate, see if a default user is active
1851 + if (obj.args.user && obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]) {
1852 + // A default user is active
1853 + func(ws, req, domain, obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]);
1854 + } else {
1855 + // If not authenticated, close the websocket connection
1856 + Debug(1, 'ERR: Websocket bad user/pass auth');
1857 + try { ws.send(JSON.stringify({ action: 'close', cause: 'noauth' })); ws.close(); } catch (e) { }
1858 + }
1859 }
1860 });
1861 return;
@@ -1865,9 +1875,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
1875 // A default user is active
1876 func(ws, req, domain, obj.users['user/' + domain.id + '/' + obj.args.user.toLowerCase()]);
1877 return;
1868 - } else if (req.session && (req.session.userid != null) && (req.session.domainid == domain.id)) {
1878 + } else if (req.session && (req.session.userid != null) && (req.session.domainid == domain.id) && (obj.users[req.session.userid])) {
1879 // This user is logged in using the ExpressJS session
1870 - func(ws, req, domain, req.session.userid);
1880 + func(ws, req, domain, obj.users[req.session.userid]);
1881 return;
1882 }
1883