From cdfcb01cfa10471fd1e512b36c42175ae42002b6 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 15 Oct 2019 15:50:11 -0700 Subject: [PATCH] Improved meshrelay security. --- meshrelay.js | 24 +++++++++++++++++++++++ meshuser.js | 9 ++++++++- package.json | 2 +- public/scripts/agent-redir-ws-0.1.0.js | 7 +++++-- views/default-min.handlebars | 27 +++++++++++++++++--------- views/default-mobile-min.handlebars | 2 +- views/default-mobile.handlebars | 19 +++++++++++++----- views/default.handlebars | 22 +++++++++++++-------- webserver.js | 5 +++-- 9 files changed, 88 insertions(+), 29 deletions(-) diff --git a/meshrelay.js b/meshrelay.js index 7798807b..33ec69b9 100644 --- a/meshrelay.js +++ b/meshrelay.js @@ -18,8 +18,18 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie obj.ws = ws; obj.id = req.query.id; obj.user = user; + obj.ruserid = null; obj.req = req; // Used in multi-server.js + // Check relay authentication + if ((user == null) && (req.query.rauth != null)) { + var rcookie = parent.parent.decodeCookie(req.query.rauth, parent.parent.loginCookieEncryptionKey, 240); // Cookie with 4 hour timeout + if (rcookie.ruserid != null) { obj.ruserid = rcookie.ruserid; } + } + + // If there is no authentication, drop this connection + if ((obj.id.startsWith('meshmessenger/') == false) && (obj.user == null) && (obj.ruserid == null)) { try { ws.close(); parent.parent.debug('relay', 'Relay: Connection with no authentication (' + cleanRemoteAddr(req.ip) + ')'); } catch (e) { console.log(e); } return; } + // Relay session count (we may remove this in the future) obj.relaySessionCounted = true; parent.relaySessionCount++; @@ -150,6 +160,20 @@ module.exports.CreateMeshRelay = function (parent, ws, req, domain, user, cookie return null; } + // Check that both connection are for the same user + if (!obj.id.startsWith('meshmessenger/')) { + var u1 = obj.user ? obj.user._id : obj.ruserid; + var u2 = relayinfo.peer1.user ? relayinfo.peer1.user._id : relayinfo.peer1.ruserid; + if (u1 != u2) { + ws.close(); + parent.parent.debug('relay', 'Relay auth mismatch: ' + obj.id + ' (' + cleanRemoteAddr(req.ip) + ')'); + delete obj.id; + delete obj.ws; + delete obj.peer; + return null; + } + } + // Connect to peer obj.peer = relayinfo.peer1; obj.peer.peer = obj; diff --git a/meshuser.js b/meshuser.js index 0b6277e3..b2dee2d8 100644 --- a/meshuser.js +++ b/meshuser.js @@ -361,7 +361,14 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use case 'authcookie': { // Renew the authentication cookie - try { ws.send(JSON.stringify({ action: 'authcookie', cookie: parent.parent.encodeCookie({ userid: user._id, domainid: domain.id }, parent.parent.loginCookieEncryptionKey) })); } catch (ex) { } + try { + console.log(req.ip); + ws.send(JSON.stringify({ + action: 'authcookie', + cookie: parent.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: cleanRemoteAddr(req.ip) }, parent.parent.loginCookieEncryptionKey), + rcookie: parent.parent.encodeCookie({ ruserid: user._id }, parent.parent.loginCookieEncryptionKey) + })); + } catch (ex) { } break; } case 'logincookie': diff --git a/package.json b/package.json index 55ac63b3..3658eb99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.4.2-k", + "version": "0.4.2-l", "keywords": [ "Remote Management", "Intel AMT", diff --git a/public/scripts/agent-redir-ws-0.1.0.js b/public/scripts/agent-redir-ws-0.1.0.js index ccec91de..531d627f 100644 --- a/public/scripts/agent-redir-ws-0.1.0.js +++ b/public/scripts/agent-redir-ws-0.1.0.js @@ -5,12 +5,13 @@ */ // Construct a MeshServer agent direction object -var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, authCookie, domainUrl) { +var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, authCookie, rauthCookie, domainUrl) { var obj = {}; obj.m = module; // This is the inner module (Terminal or Desktop) module.parent = obj; obj.meshserver = meshserver; obj.authCookie = authCookie; + obj.rauthCookie = rauthCookie; obj.State = 0; obj.nodeid = null; obj.socket = null; @@ -49,7 +50,9 @@ var CreateAgentRedirect = function (meshserver, module, serverPublicNamePort, au obj.socket.onclose = obj.xxOnSocketClosed; obj.xxStateChange(1); //obj.meshserver.send({ action: 'msg', type: 'tunnel', nodeid: obj.nodeid, value: url2 }); - obj.meshserver.send({ action: 'msg', type: 'tunnel', nodeid: obj.nodeid, value: "*" + domainUrl + "meshrelay.ashx?p=" + obj.protocol + "&nodeid=" + nodeid + "&id=" + obj.tunnelid, usage: obj.protocol }); + var rurl = "*" + domainUrl + "meshrelay.ashx?p=" + obj.protocol + "&nodeid=" + nodeid + "&id=" + obj.tunnelid; + if ((rauthCookie != null) && (rauthCookie != '')) { rurl += '&rauth=' + rauthCookie; } + obj.meshserver.send({ action: 'msg', type: 'tunnel', nodeid: obj.nodeid, value: rurl, usage: obj.protocol }); //obj.debug("Agent Redir Start: " + url); } diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 66e162b5..426bf0b9 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1,4 +1,4 @@ - {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

{{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

{{{title}}}
{{{title}}}
{{{title2}}}
\ No newline at end of file + {{{title}}}
{{{title}}}
{{{title2}}}
\ No newline at end of file diff --git a/views/default-mobile.handlebars b/views/default-mobile.handlebars index 4e5fdfaf..552f7f87 100644 --- a/views/default-mobile.handlebars +++ b/views/default-mobile.handlebars @@ -603,6 +603,8 @@ var domain = "{{{domain}}}"; var domainUrl = "{{{domainurl}}}"; var authCookie = "{{{authCookie}}}"; + var authRelayCookie = "{{{authRelayCookie}}}"; + var authCookieRenewTimer = null; var meshserver = null; var xdr = null; var serverinfo = null; @@ -658,13 +660,14 @@ if (errorCode == 'noauth') { QH('p0span', 'Unable to perform authentication'); return; } if (prevState == 2) { setTimeout(serverPoll, 5000); } else { QH('p0span', 'Unable to connect web socket'); } // Clean up here - + if (authCookieRenewTimer != null) { clearInterval(authCookieRenewTimer); authCookieRenewTimer = null; } } else if (state == 2) { // Fetch list of meshes, nodes, files meshserver.send({ action: 'meshes' }); meshserver.send({ action: 'nodes' }); meshserver.send({ action: 'files' }); if (xxcurrentView < 2) { go(2); } + authCookieRenewTimer = setInterval(function () { meshserver.send({ action: 'authcookie' }); }, 1800000); // Request a cookie refresh every 30 minutes. } QV('topMenuIcon', state == 2); } @@ -715,6 +718,12 @@ QV('logoutMenuOption', ((features & 4) == 0) && (serverinfo.domainauth == false)); // Hide logout if in single user mode or domain authentication break; } + case 'authcookie': { + // Got an authentication cookie refresh + authCookie = message.cookie; + authRelayCookie = message.rcookie; + break; + } case 'userinfo': { userinfo = message.userinfo; QH('p3userName', userinfo.name); @@ -2312,7 +2321,7 @@ desktop.contype = 2; } else { // Setup the Mesh Agent remote desktop - desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort, authCookie, domainUrl); + desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); desktop.debugmode = debugmode; desktop.m.debugmode = debugmode; desktop.attemptWebRTC = attemptWebRTC; @@ -2670,7 +2679,7 @@ function connectFiles(e) { if (!files) { // Setup a mesh agent files - files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort, authCookie, domainUrl); + files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); files.attemptWebRTC = attemptWebRTC; files.onStateChanged = onFilesStateChange; files.Start(filesNode._id); @@ -2896,7 +2905,7 @@ // Called by the html page to start a download, arguments are: path, file name and file size. function p13downloadfile(x, y, z) { if (xxdialogMode) return; - downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort, authCookie, domainUrl); // Create our websocket file transport + downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); // Create our websocket file transport downloadFile.ctrlMsgAllowed = false; downloadFile.onStateChanged = onFileDownloadStateChange; downloadFile.xpath = decodeURIComponent(x); @@ -2983,7 +2992,7 @@ // Connect again function p13uploadReconnect() { - uploadFile.ws = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotUploadData), serverPublicNamePort, authCookie, domainUrl); + uploadFile.ws = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotUploadData), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); uploadFile.ws.attemptWebRTC = false; uploadFile.ws.ctrlMsgAllowed = false; uploadFile.ws.onStateChanged = onFileUploadStateChange; diff --git a/views/default.handlebars b/views/default.handlebars index b30d7fd7..c84e196c 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -184,9 +184,9 @@ -     +       - +   @@ -1017,6 +1017,7 @@ var domain = "{{{domain}}}"; var domainUrl = "{{{domainurl}}}"; var authCookie = "{{{authCookie}}}"; + var authRelayCookie = "{{{authRelayCookie}}}"; var authCookieRenewTimer = null; var multiDesktop = {}; var multiDesktopFilter = null; @@ -1466,6 +1467,7 @@ case 'authcookie': { // Got an authentication cookie refresh authCookie = message.cookie; + authRelayCookie = message.rcookie; break; } case 'serverinfo': { @@ -2664,6 +2666,10 @@ if (typeof deviceHeaderCount[node.state] == 'undefined') { deviceHeaderCount[node.state] = 1; } else { deviceHeaderCount[node.state]++; } } + // Display "connect all" and "auto" + QV('kvmMultiConnectButtonSpan', (kvmDivs.length < 16)); + QV('kvmAutoConnectButtonSpan', (kvmDivs.length < 16)); + // If displaying devices by groups, sort the group names and display the devices. if (sort == 3) { if (view == 2) { r = '
UserAddressConnectivity'; } @@ -2877,7 +2883,7 @@ multiDesktop[nodeid] = desk; } else if (contype == 1) { // Setup the Mesh Agent remote desktop - desk = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('kvmid_' + shortid), serverPublicNamePort, authCookie, domainUrl); + desk = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('kvmid_' + shortid), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); desk.shortid = shortid; desk.attemptWebRTC = attemptWebRTC; desk.onStateChanged = onMultiDesktopStateChange; @@ -5038,7 +5044,7 @@ desktop.contype = 2; } else { // Setup the Mesh Agent remote desktop - desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort, authCookie, domainUrl); + desktop = CreateAgentRedirect(meshserver, CreateAgentRemoteDesktop('Desk'), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); desktop.debugmode = debugmode; desktop.m.debugmode = debugmode; desktop.attemptWebRTC = attemptWebRTC; @@ -5755,7 +5761,7 @@ } } if ((e && (e.shiftKey == true)) || (options && (options.powershell))) { termoptions.protocol = 6; } - terminal = CreateAgentRedirect(meshserver, CreateAmtRemoteTerminal('Term', termoptions), serverPublicNamePort, authCookie, domainUrl); + terminal = CreateAgentRedirect(meshserver, CreateAmtRemoteTerminal('Term', termoptions), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); terminal.debugmode = debugmode; terminal.m.debugmode = debugmode; terminal.m.onTitleChange = function (sender, title) { QH('termtitle', ' - ' + EscapeHtml(title)); } @@ -5891,7 +5897,7 @@ p13clearConsoleMsg(); if (!files) { // Setup a mesh agent files - files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort, authCookie, domainUrl); + files = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotFiles), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); files.attemptWebRTC = attemptWebRTC; files.onStateChanged = onFilesStateChange; files.onConsoleMessageChange = function () { @@ -6188,7 +6194,7 @@ // Called by the html page to start a download, arguments are: path, file name and file size. function p13downloadfile(x, y, z) { if (xxdialogMode) return; - downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort, authCookie, domainUrl); // Create our websocket file transport + downloadFile = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotDownloadData), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); // Create our websocket file transport downloadFile.ctrlMsgAllowed = false; downloadFile.onStateChanged = onFileDownloadStateChange; downloadFile.xpath = decodeURIComponent(x); @@ -6297,7 +6303,7 @@ // Connect again function p13uploadReconnect() { - uploadFile.ws = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotUploadData), serverPublicNamePort, authCookie, domainUrl); + uploadFile.ws = CreateAgentRedirect(meshserver, CreateRemoteFiles(p13gotUploadData), serverPublicNamePort, authCookie, authRelayCookie, domainUrl); uploadFile.ws.attemptWebRTC = false; uploadFile.ws.ctrlMsgAllowed = false; uploadFile.ws.onStateChanged = onFileUploadStateChange; diff --git a/webserver.js b/webserver.js index ed7ddc27..2e0ad5c0 100644 --- a/webserver.js +++ b/webserver.js @@ -1510,6 +1510,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Create a authentication cookie const authCookie = obj.parent.encodeCookie({ userid: user._id, domainid: domain.id, ip: cleanRemoteAddr(req.ip) }, obj.parent.loginCookieEncryptionKey); + const authRelayCookie = obj.parent.encodeCookie({ ruserid: user._id, domainid: domain.id }, obj.parent.loginCookieEncryptionKey); // Send the master web application if ((!obj.args.user) && (obj.args.nousers != true) && (nologout == false)) { logoutcontrol += ' Logout'; } // If a default user is in use or no user mode, don't display the logout button @@ -1522,7 +1523,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { parent.debug('web', 'handleRootRequestEx: success.'); obj.db.Get('ws' + user._id, function (err, states) { var webstate = (states.length == 1) ? states[0].state : ''; - res.render(getRenderPage('default', req), { authCookie: authCookie, viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, extitle: encodeURIComponent(domain.title), extitle2: encodeURIComponent(domain.title2), domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, sessiontime: args.sessiontime, mpspass: args.mpspass, passRequirements: passRequirements, webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'), footer: (domain.footer == null) ? '' : domain.footer, webstate: encodeURIComponent(webstate), pluginHandler: (parent.pluginHandler == null)?'null':parent.pluginHandler.prepExports() }); + res.render(getRenderPage('default', req), { authCookie: authCookie, authRelayCookie: authRelayCookie, viewmode: viewmode, currentNode: currentNode, logoutControl: logoutcontrol, title: domain.title, title2: domain.title2, extitle: encodeURIComponent(domain.title), extitle2: encodeURIComponent(domain.title2), domainurl: domain.url, domain: domain.id, debuglevel: parent.debugLevel, serverDnsName: obj.getWebServerName(domain), serverRedirPort: args.redirport, serverPublicPort: httpsPort, noServerBackup: (args.noserverbackup == 1 ? 1 : 0), features: features, sessiontime: args.sessiontime, mpspass: args.mpspass, passRequirements: passRequirements, webcerthash: Buffer.from(obj.webCertificateFullHashs[domain.id], 'binary').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'), footer: (domain.footer == null) ? '' : domain.footer, webstate: encodeURIComponent(webstate), pluginHandler: (parent.pluginHandler == null)?'null':parent.pluginHandler.prepExports() }); }); } else { // Send back the login application @@ -3460,7 +3461,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // This is a encrypted cookie authentication var cookie = obj.parent.decodeCookie(req.query.auth, obj.parent.loginCookieEncryptionKey, 240); // Cookie with 4 hour timeout if ((cookie == null) && (obj.parent.multiServer != null)) { cookie = obj.parent.decodeCookie(req.query.auth, obj.parent.serverKey, 240); } // Try the server key - //if ((cookie != null) && (cookie.ip != null) && (cookie.ip != cleanRemoteAddr(req.ip))) { cookie = null; } // If the cookie if binded to an IP address, check here. + if ((cookie != null) && (cookie.ip != null) && (cookie.ip != cleanRemoteAddr(req.ip) && (cookie.ip != req.ip))) { cookie = null; } // If the cookie if binded to an IP address, check here. if ((cookie != null) && (obj.users[cookie.userid]) && (cookie.domainid == domain.id)) { // Valid cookie, we are authenticated func(ws, req, domain, obj.users[cookie.userid], cookie);