From 815fa1b0bbe394fb050b54d2e90f733ef8cd932e Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Tue, 7 Jul 2020 23:56:08 -0700 Subject: [PATCH] Added auto agent crash dump server upload option. --- agents/meshcore.js | 39 +++++++++++++++---- meshagent.js | 8 ++++ webserver.js | 96 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 131 insertions(+), 12 deletions(-) diff --git a/agents/meshcore.js b/agents/meshcore.js index e7732280..a33fe2ad 100644 --- a/agents/meshcore.js +++ b/agents/meshcore.js @@ -959,6 +959,7 @@ function createMeshCore(agent) { break; } case 'coredump': + // Set the current agent coredump situation. if (data.value === true) { // TODO: This replace() below is not ideal, would be better to remove the .exe at the end instead of replace. process.coreDumpLocation = (process.platform == 'win32') ? (process.execPath.replace('.exe', '.dmp')) : (process.execPath + '.dmp'); @@ -967,12 +968,12 @@ function createMeshCore(agent) { } break; case 'getcoredump': + // Ask the agent if a core dump is currently available, if yes, also return the hash of the agent. var r = { action: 'getcoredump', value: (process.coreDumpLocation != null) }; - if (process.platform == 'win32') { - r.exists = r.value ? fs.existsSync(process.coreDumpLocation) : false; - } else { - r.exists = (r.value && (process.cwd() != '//')) ? fs.existsSync(process.cwd() + 'core') : false; - } + var coreDumpPath = null; + if (process.platform == 'win32') { coreDumpPath = process.coreDumpLocation; } else { coreDumpPath = (process.cwd() != '//') ? fs.existsSync(process.cwd() + 'core') : null; } + if ((coreDumpPath != null) && (fs.existsSync(coreDumpPath))) { r.exists = (db.Get('CoreDumpTime') != require('fs').statSync(coreDumpPath).mtime); } + if (r.exists == true) { r.agenthashhex = getSHA384FileHash(process.execPath).toString('hex'); } mesh.SendCommand(JSON.stringify(r)); default: // Unknown action, ignore it. @@ -1913,6 +1914,17 @@ function createMeshCore(agent) { } break; } + case 'markcoredump': { + // If we are asking for the coredump file, set the right path. + var coreDumpPath = null; + if (process.platform == 'win32') { + if (fs.existsSync(process.coreDumpLocation)) { coreDumpPath = process.coreDumpLocation; } + } else { + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { coreDumpPath = process.cwd() + 'core'; } + } + if (coreDumpPath != null) { db.Put('CoreDumpTime', require('fs').statSync(coreDumpPath).mtime); } + break; + } case 'rename': { // Rename a file or folder var oldfullpath = obj.path.join(cmd.path, cmd.oldname); @@ -1925,13 +1937,20 @@ function createMeshCore(agent) { // Download a file var sendNextBlock = 0; if (cmd.sub == 'start') { // Setup the download + if ((cmd.path == null) && (cmd.ask == 'coredump')) { // If we are asking for the coredump file, set the right path. + if (process.platform == 'win32') { + if (fs.existsSync(process.coreDumpLocation)) { cmd.path = process.coreDumpLocation; } + } else { + if ((process.cwd() != '//') && fs.existsSync(process.cwd() + 'core')) { cmd.path = process.cwd() + 'core'; } + } + } MeshServerLog('Download: \"' + cmd.path + '\"', this.httprequest); - if (this.filedownload != null) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } + if ((cmd.path == null) || (this.filedownload != null)) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } this.filedownload = { id: cmd.id, path: cmd.path, ptr: 0 } try { this.filedownload.f = fs.openSync(this.filedownload.path, 'rbN'); } catch (e) { this.write({ action: 'download', sub: 'cancel', id: this.filedownload.id }); delete this.filedownload; } if (this.filedownload) { this.write({ action: 'download', sub: 'start', id: cmd.id }); } } else if ((this.filedownload != null) && (cmd.id == this.filedownload.id)) { // Download commands - if (cmd.sub == 'startack') { sendNextBlock = 8; } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; } + if (cmd.sub == 'startack') { sendNextBlock = ((typeof cmd.ack == 'number') ? cmd.ack : 8); } else if (cmd.sub == 'stop') { delete this.filedownload; } else if (cmd.sub == 'ack') { sendNextBlock = 1; } } // Send the next download block(s) while (sendNextBlock > 0) { @@ -2278,7 +2297,7 @@ function createMeshCore(agent) { } case 'coredump': if (args['_'].length != 1) { - response = "Proper usage: coredump on|off|status"; // Display usage + response = "Proper usage: coredump on|off|status|clear"; // Display usage } else { switch (args['_'][0].toLowerCase()) { @@ -2300,6 +2319,10 @@ function createMeshCore(agent) { } } break; + case 'clear': + db.Put('CoreDumpTime', null); + response = 'coredump db cleared'; + break; default: response = "Proper usage: coredump on|off|status"; // Display usage break; diff --git a/meshagent.js b/meshagent.js index 72d1eee4..43fa539f 100644 --- a/meshagent.js +++ b/meshagent.js @@ -1375,6 +1375,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { //console.log('CoreDump for agent ' + obj.remoteaddrport); obj.coreDumpPresent = true; // TODO: We need to look at getting the dump uploaded to the server. + if (typeof command.agenthashhex == 'string') { obj.RequestCoreDump(command.agenthashhex); } } break; } @@ -1555,6 +1556,13 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { }); } + // Request that the core dump file on this agent be uploaded to the server + obj.RequestCoreDump = function (agenthashhex) { + if (agenthashhex.length > 16) { agenthashhex = agenthashhex.substring(0, 16); } + const cookie = parent.parent.encodeCookie({ a: 'aft', b: 'coredump', c: obj.agentInfo.agentId + '-' + agenthashhex + '-' + obj.nodeid + '.dmp' }, parent.parent.loginCookieEncryptionKey); + obj.send('{"action":"msg","type":"tunnel","value":"*/' + (((domain.dns == null) && (domain.id != '')) ? (domain.id + '/') : '') + 'agenttransfer.ashx?c=' + cookie + '","rights":"4294967295"}'); + } + // Generate a random Intel AMT password function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); } function getRandomAmtPassword() { var p; do { p = Buffer.from(parent.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; } diff --git a/webserver.js b/webserver.js index c4cb7ac7..8519be44 100644 --- a/webserver.js +++ b/webserver.js @@ -2998,9 +2998,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // See if we need to create the folder var domainx = 'domain'; if (domain.id.length > 0) { domainx = 'domain-' + usersplit[1]; } - try { obj.fs.mkdirSync(obj.parent.filespath); } catch (e) { } - try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (e) { } - try { obj.fs.mkdirSync(xfile.fullpath); } catch (e) { } + try { obj.fs.mkdirSync(obj.parent.filespath); } catch (ex) { } + try { obj.fs.mkdirSync(obj.parent.path.join(obj.parent.filespath, domainx)); } catch (ex) { } + try { obj.fs.mkdirSync(xfile.fullpath); } catch (ex) { } // Upload method where all the file data is within the fields. var names = fields.name[0].split('*'), sizes = fields.size[0].split('*'), types = fields.type[0].split('*'), datas = fields.data[0].split('*'); @@ -3423,7 +3423,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } }); - } + } // Handle a Intel AMT activation request function handleAmtActivateWebSocket(ws, req) { @@ -3614,6 +3614,90 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { ws.on('close', function (req) { }); } + // Setup agent to/from server file transfer handler + function handleAgentFileTransfer(ws, req) { + var domain = checkAgentIpAddress(ws, req); + if (domain == null) { parent.debug('web', 'Got agent file transfer connection with bad domain or blocked IP address ' + req.clientIp + ', dropping.'); ws.close(); return; } + if (req.query.c == null) { parent.debug('web', 'Got agent file transfer connection without a cookie from ' + req.clientIp + ', dropping.'); ws.close(); return; } + var c = obj.parent.decodeCookie(req.query.c, obj.parent.loginCookieEncryptionKey, 10); // 10 minute timeout + if ((c == null) || (c.a != 'aft')) { parent.debug('web', 'Got agent file transfer connection with invalid cookie from ' + req.clientIp + ', dropping.'); ws.close(); return; } + ws.xcmd = c.b; ws.xarg = c.c, ws.xfilelen = 0; + ws.send('c'); // Indicate connection of the tunnel. In this case, we are the termination point. + ws.send('5'); // Indicate we want to perform file transfers (5 = Files). + if (ws.xcmd == 'coredump') { + // Check the agent core dump folder if not already present. + var coreDumpPath = obj.path.join(parent.datapath, 'coredumps'); + if (obj.fs.existsSync(coreDumpPath) == false) { try { obj.fs.mkdirSync(coreDumpPath); } catch (ex) { } } + ws.xfilepath = obj.path.join(parent.datapath, 'coredumps', ws.xarg); + ws.xid = 'coredump'; + ws.send(JSON.stringify({ action: 'download', sub: 'start', ask: 'coredump', id: 'coredump' })); // Ask for a directory (test) + } + + // When data is received from the web socket, echo it back + ws.on('message', function (data) { + if (typeof data == 'string') { + // Control message + var cmd = null; + try { cmd = JSON.parse(data); } catch (ex) { } + if ((cmd == null) || (cmd.action != 'download') || (cmd.sub == null)) return; + switch (cmd.sub) { + case 'start': { + // Perform an async file open + var callback = function onFileOpen(err, fd) { + onFileOpen.xws.xfile = fd; + onFileOpen.xws.send(JSON.stringify({ action: 'download', sub: 'startack', id: onFileOpen.xws.xid, ack: 1 })); // Ask for a directory (test) + }; + callback.xws = this; + obj.fs.open(this.xfilepath, 'w', callback) + break; + } + } + } else { + // Binary message + if (data.length < 4) return; + var flags = data.readInt32BE(0); + if ((data.length > 4)) { + // Write the file + this.xfilelen += (data.length - 4); + try { + var callback = function onFileDataWritten(err, bytesWritten, buffer) { + if (onFileDataWritten.xflags & 1) { + // End of file + parent.debug('web', "Completed downloads of agent dumpfile, " + onFileDataWritten.xws.xfilelen + " bytes."); + if (onFileDataWritten.xws.xfile) { try { obj.fs.close(onFileDataWritten.xws.xfile, function (err) { }); } catch (ex) { } } + onFileDataWritten.xws.send(JSON.stringify({ action: 'markcoredump' })); // Ask to delete the core dump file + try { onFileDataWritten.xws.close(); } catch (ex) { } + } else { + // Send ack + onFileDataWritten.xws.send(JSON.stringify({ action: 'download', sub: 'ack', id: onFileDataWritten.xws.xid })); // Ask for a directory (test) + } + }; + callback.xws = this; + callback.xflags = flags; + obj.fs.write(this.xfile, data, 4, data.length - 4, callback); + } catch (ex) { } + } else { + if (flags & 1) { + // End of file + parent.debug('web', "Completed downloads of agent dumpfile, " + this.xfilelen + " bytes."); + if (this.xfile) { try { obj.fs.close(this.xfile, function (err) { }); } catch (ex) { } } + this.send(JSON.stringify({ action: 'markcoredump' })); // Ask to delete the core dump file + try { this.close(); } catch (ex) { } + } else { + // Send ack + this.send(JSON.stringify({ action: 'download', sub: 'ack', id: this.xid })); // Ask for a directory (test) + } + } + } + }); + + // If error, do nothing. + ws.on('error', function (err) { console.log('Agent file transfer server error from ' + req.clientIp + ', ' + err.toString().split('\r')[0] + '.'); }); + + // If closed, do nothing + ws.on('close', function (req) { }); + } + // Handle the web socket echo request, just echo back the data sent function handleEchoWebSocket(ws, req) { const domain = checkUserIpAddress(ws, req); @@ -4428,6 +4512,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { obj.app.get(url + 'player.htm', handlePlayerRequest); obj.app.get(url + 'player', handlePlayerRequest); obj.app.ws(url + 'amtactivate', handleAmtActivateWebSocket); + obj.app.ws(url + 'agenttransfer.ashx', handleAgentFileTransfer); // Setup agent to/from server file transfer handler obj.app.ws(url + 'meshrelay.ashx', function (ws, req) { PerformWSSessionAuth(ws, req, true, function (ws1, req1, domain, user, cookie) { if (((parent.config.settings.desktopmultiplex === true) || (domain.desktopmultiplex === true)) && (req.query.p == 2)) { @@ -4830,6 +4915,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } }); }); + + // Setup agent to/from server file transfer handler + obj.agentapp.ws(url + 'agenttransfer.ashx', handleAgentFileTransfer); // Setup agent to/from server file transfer handler } // Memory Tracking