From 0bf459bb51b6050b756f583adf0f0c3ed7e2758e Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 30 Aug 2022 17:53:27 -0700 Subject: [PATCH] Many web relay improvements and fixes (#4467, #4456) --- apprelays.js | 17 ++++++++++++++--- meshcentral.js | 4 ++-- webrelayserver.js | 2 +- webserver.js | 2 +- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/apprelays.js b/apprelays.js index a48cfa09..61b27a34 100644 --- a/apprelays.js +++ b/apprelays.js @@ -260,6 +260,15 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) { // Called when we need to close the tunnel because the response stream has closed function handleResponseClosure() { obj.close(); } + // Return copkie name and values + function parseRequestCookies(cookiesString) { + var r = {}; + if (typeof cookiesString != 'string') return r; + var cookieString = cookiesString.split('; '); + for (var i in cookieString) { var j = cookieString[i].indexOf('='); if (j > 0) { r[cookieString[i].substring(0, j)] = cookieString[i].substring(j + 1); } } + return r; + } + // Process a HTTP request obj.processRequest = function (req, res) { if (obj.relayActive == false) { console.log("ERROR: Attempt to use an unconnected tunnel"); return false; } @@ -277,6 +286,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) { for (var i in req.headers) { if (blockedHeaders.indexOf(i) == -1) { request += i + ': ' + req.headers[i] + '\r\n'; } } var cookieStr = ''; for (var i in parent.webCookies) { if (cookieStr != '') { cookieStr += '; ' } cookieStr += (i + '=' + parent.webCookies[i].value); } + var reqCookies = parseRequestCookies(req.headers.cookie); + for (var i in reqCookies) { if ((i != 'xid') && (i != 'xid.sig')) { if (cookieStr != '') { cookieStr += '; ' } cookieStr += (i + '=' + reqCookies[i]); } } if (cookieStr.length > 0) { request += 'cookie: ' + cookieStr + '\r\n' } // If we have session cookies, set them in the header here request += '\r\n'; @@ -677,7 +688,7 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) { // If there is a header, send it if (header != null) { obj.res.status(parseInt(header.Directive[1])); // Set the status - const blockHeaders = ['Directive', 'sec-websocket-extensions', 'connection', 'transfer-encoding']; // We do not forward these headers + const blockHeaders = ['Directive', 'sec-websocket-extensions', 'connection', 'transfer-encoding', 'last-modified', 'content-security-policy', 'cache-control']; // We do not forward these headers for (var i in header) { if (i == 'set-cookie') { for (var ii in header[i]) { @@ -704,8 +715,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) { } else if (blockHeaders.indexOf(i) == -1) { obj.res.set(i, header[i]); } // Set the headers if not blocked } - obj.res.set('Content-Security-Policy', "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:;"); // Set an "allow all" policy, see if the can restrict this in the future - obj.res.set('Cache-Control', 'no-cache'); // Tell the browser not to cache the responses since since the relay port can be used for many relays + obj.res.set('Content-Security-Policy', "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';"); // Set an "allow all" policy, see if the can restrict this in the future + obj.res.set('Cache-Control', 'no-store'); // Tell the browser not to cache the responses since since the relay port can be used for many relays } // If there is data, send it diff --git a/meshcentral.js b/meshcentral.js index b6ecead2..ad21a60e 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -3471,7 +3471,7 @@ function CreateMeshCentralServer(config, args) { obj.decodeCookie = function (cookie, key, timeout) { if (cookie == null) return null; var r = obj.decodeCookieAESGCM(cookie, key, timeout); - if (r == null) { r = obj.decodeCookieAESSHA(cookie, key, timeout); } + if (r === -1) { r = obj.decodeCookieAESSHA(cookie, key, timeout); } // If decodeCookieAESGCM() failed to decode, try decodeCookieAESSHA() if ((r == null) && (obj.args.cookieencoding == null) && (cookie.length != 64) && ((cookie == cookie.toLowerCase()) || (cookie == cookie.toUpperCase()))) { obj.debug('cookie', 'Upper/Lowercase cookie, try "CookieEncoding":"hex" in settings section of config.json.'); console.log('Upper/Lowercase cookie, try "CookieEncoding":"hex" in settings section of config.json.'); @@ -3523,7 +3523,7 @@ function CreateMeshCentralServer(config, args) { } obj.debug('cookie', 'Decoded AESGCM cookie: ' + JSON.stringify(o)); return o; - } catch (ex) { obj.debug('cookie', 'ERR: Bad AESGCM cookie due to exception: ' + ex); return null; } + } catch (ex) { obj.debug('cookie', 'ERR: Bad AESGCM cookie due to exception: ' + ex); return -1; } }; // Decode a cookie back into an object using a key using AES256 / HMAC-SHA384. Return null if it's not a valid cookie. (key must be 80 bytes or more) diff --git a/webrelayserver.js b/webrelayserver.js index bac34b73..34f12bfd 100644 --- a/webrelayserver.js +++ b/webrelayserver.js @@ -188,7 +188,7 @@ module.exports.CreateWebRelayServer = function (parent, db, args, certificates, // Decode and check if this relay cookie is valid var userid, domainid, domain, nodeid, addr, port, appid, webSessionId, expire; - const urlCookie = obj.parent.decodeCookie(req.query.c, parent.loginCookieEncryptionKey); + const urlCookie = obj.parent.decodeCookie(req.query.c, parent.loginCookieEncryptionKey, 32); // Allow cookies up to 32 minutes old. The web page will renew this cookie every 30 minutes. if (urlCookie == null) { res.sendStatus(404); return; } // Decode the incomign cookie diff --git a/webserver.js b/webserver.js index 316d5c96..c06a6b56 100644 --- a/webserver.js +++ b/webserver.js @@ -6574,7 +6574,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF // Decode and check if this relay cookie is valid var userid, domainid, domain, nodeid, addr, port, appid, webSessionId, expire; - const urlCookie = obj.parent.decodeCookie(req.query.c, parent.loginCookieEncryptionKey); + const urlCookie = obj.parent.decodeCookie(req.query.c, parent.loginCookieEncryptionKey, 32); // Allow cookies up to 32 minutes old. The web page will renew this cookie every 30 minutes. if (urlCookie == null) { res.sendStatus(404); return; } // Decode the incomign cookie