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