Added Shell command to MeshCtrl.js

This commit is contained in:
Ylian Saint-Hilaire 2020-07-13 15:06:45 -07:00
parent 143a39fca7
commit c8b75d70d6
4 changed files with 95 additions and 11 deletions

View File

@ -7,7 +7,7 @@ try { require('ws'); } catch (ex) { console.log('Missing module "ws", type "npm
var settings = {}; var settings = {};
const crypto = require('crypto'); const crypto = require('crypto');
const args = require('minimist')(process.argv.slice(2)); const args = require('minimist')(process.argv.slice(2));
const possibleCommands = ['listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand']; const possibleCommands = ['listusers', 'listusersessions', 'listdevicegroups', 'listdevices', 'listusersofdevicegroup', 'serverinfo', 'userinfo', 'adduser', 'removeuser', 'adddevicegroup', 'removedevicegroup', 'broadcast', 'showevents', 'addusertodevicegroup', 'removeuserfromdevicegroup', 'addusertodevice', 'removeuserfromdevice', 'sendinviteemail', 'generateinvitelink', 'config', 'movetodevicegroup', 'deviceinfo', 'addusergroup', 'listusergroups', 'removeusergroup', 'runcommand', 'shell'];
if (args.proxy != null) { try { require('https-proxy-agent'); } catch (ex) { console.log('Missing module "https-proxy-agent", type "npm install https-proxy-agent" to install it.'); return; } } if (args.proxy != null) { try { require('https-proxy-agent'); } catch (ex) { console.log('Missing module "https-proxy-agent", type "npm install https-proxy-agent" to install it.'); return; } }
if (args['_'].length == 0) { if (args['_'].length == 0) {
@ -42,6 +42,7 @@ if (args['_'].length == 0) {
console.log(" Broadcast - Display a message to all online users."); console.log(" Broadcast - Display a message to all online users.");
console.log(" ShowEvents - Display real-time server events in JSON format."); console.log(" ShowEvents - Display real-time server events in JSON format.");
console.log(" RunCommand - Run a shell command on a remote device."); console.log(" RunCommand - Run a shell command on a remote device.");
console.log(" Shell - Access command shell of a remote device.");
console.log("\r\nSupported login arguments:"); console.log("\r\nSupported login arguments:");
console.log(" --url [wss://server] - Server url, wss://localhost:443 is default."); console.log(" --url [wss://server] - Server url, wss://localhost:443 is default.");
console.log(" --loginuser [username] - Login username, admin is default."); console.log(" --loginuser [username] - Login username, admin is default.");
@ -165,6 +166,11 @@ if (args['_'].length == 0) {
else { ok = true; } else { ok = true; }
break; break;
} }
case 'shell': {
if (args.id == null) { console.log("Missing device id, use --id [deviceid]"); }
else { ok = true; }
break;
}
case 'help': { case 'help': {
if (args['_'].length < 2) { if (args['_'].length < 2) {
console.log("Get help on an action. Type:\r\n\r\n help [action]\r\n\r\nPossible actions are: " + possibleCommands.join(', ') + '.'); console.log("Get help on an action. Type:\r\n\r\n help [action]\r\n\r\nPossible actions are: " + possibleCommands.join(', ') + '.');
@ -428,6 +434,16 @@ if (args['_'].length == 0) {
console.log(" --powershell - Run in Windows PowerShell."); console.log(" --powershell - Run in Windows PowerShell.");
break; break;
} }
case 'shell': {
console.log("Access a command shell on a remote device, Example usages:\r\n");
console.log(" MeshCtrl Shell --id deviceid");
console.log(" MeshCtrl Shell --id deviceid --powershell");
console.log("\r\nRequired arguments:\r\n");
console.log(" --id [deviceid] - The device identifier.");
console.log("\r\nOptional arguments:\r\n");
console.log(" --powershell - Run a Windows PowerShell.");
break;
}
default: { default: {
console.log("Get help on an action. Type:\r\n\r\n help [action]\r\n\r\nPossible actions are: " + possibleCommands.join(', ') + '.'); console.log("Get help on an action. Type:\r\n\r\n help [action]\r\n\r\nPossible actions are: " + possibleCommands.join(', ') + '.');
} }
@ -790,6 +806,10 @@ function serverConnect() {
ws.send(JSON.stringify({ action: 'runcommands', nodeids: [args.id], type: ((args.powershell) ? 2 : 0), cmds: args.run, responseid: 'meshctrl' })); ws.send(JSON.stringify({ action: 'runcommands', nodeids: [args.id], type: ((args.powershell) ? 2 : 0), cmds: args.run, responseid: 'meshctrl' }));
break; break;
} }
case 'shell': {
ws.send("{\"action\":\"authcookie\"}");
break;
}
} }
}); });
@ -811,6 +831,7 @@ function serverConnect() {
} }
switch (data.action) { switch (data.action) {
case 'serverinfo': { // SERVERINFO case 'serverinfo': { // SERVERINFO
settings.currentDomain = data.serverinfo.domain;
if (settings.cmd == 'serverinfo') { if (settings.cmd == 'serverinfo') {
if (args.json) { if (args.json) {
console.log(JSON.stringify(data.serverinfo, ' ', 2)); console.log(JSON.stringify(data.serverinfo, ' ', 2));
@ -821,6 +842,15 @@ function serverConnect() {
} }
break; break;
} }
case 'authcookie': { // SHELL
if (settings.cmd == 'shell') {
if ((args.id.split('/') != 3) && (settings.currentDomain != null)) { args.id = 'node/' + settings.currentDomain + '/' + args.id; }
var id = getRandomHex(6);
ws.send(JSON.stringify({ action: 'msg', nodeid: args.id, type: 'tunnel', usage: 1, value: '*/meshrelay.ashx?p=1&nodeid=' + args.id + '&id=' + id + '&rauth=' + data.rcookie, responseid: 'meshctrl' }));
connectShell(url.replace('/control.ashx', '/meshrelay.ashx?browser=1&p=1&nodeid=' + args.id + '&id=' + id + '&rauth=' + data.cookie));
}
break;
}
case 'userinfo': { // USERINFO case 'userinfo': { // USERINFO
if (settings.cmd == 'userinfo') { if (settings.cmd == 'userinfo') {
if (args.json) { if (args.json) {
@ -858,6 +888,7 @@ function serverConnect() {
} }
break; break;
} }
case 'msg': // SHELL
case 'adduser': // ADDUSER case 'adduser': // ADDUSER
case 'deleteuser': // REMOVEUSER case 'deleteuser': // REMOVEUSER
case 'createmesh': // ADDDEVICEGROUP case 'createmesh': // ADDDEVICEGROUP
@ -1055,6 +1086,54 @@ function serverConnect() {
}); });
} }
// Connect tunnel to a remote agent shell
function connectShell(url) {
// Setup WebSocket options
var options = { rejectUnauthorized: false, checkServerIdentity: onVerifyServer }
// Setup the HTTP proxy if needed
if (args.proxy != null) { const HttpsProxyAgent = require('https-proxy-agent'); options.agent = new HttpsProxyAgent(require('url').parse(args.proxy)); }
// Connect the WebSocket
console.log('Connecting...');
const WebSocket = require('ws');
settings.tunnelwsstate = 0;
settings.tunnelws = new WebSocket(url, options);
settings.tunnelws.on('open', function () { console.log('Waiting for Agent...'); }); // Wait for agent connection
settings.tunnelws.on('close', function () { console.log('Connection Closed.'); process.exit(); });
settings.tunnelws.on('error', function (err) { console.log(err); process.exit(); });
settings.tunnelws.on('message', function (rawdata) {
var data = rawdata.toString();
if (settings.tunnelwsstate == 1) {
process.stdout.write(data);
} else if (settings.tunnelwsstate == 0) {
if (data == 'c') {
// Send terminal size
var termSize = null;
if (typeof process.stdout.getWindowSize == 'function') { termSize = process.stdout.getWindowSize(); }
if (termSize != null) { settings.tunnelws.send(JSON.stringify({ ctrlChannel: '102938', type: 'options', cols: termSize[0], rows: termSize[1] })); }
console.log('Connected.');
settings.tunnelwsstate = 1;
settings.tunnelws.send('1');
}
else if (data == 'cr') { console.log('Connected, session is being recorded.'); settings.tunnelwsstate = 1; settings.tunnelws.send('1'); }
process.stdin.setEncoding('utf8');
process.stdin.setRawMode(true);
process.stdout.setEncoding('utf8');
process.stdin.unpipe(process.stdout);
process.stdout.unpipe(process.stdin);
process.stdin.on('data', function (data) { settings.tunnelws.send(Buffer.from(data)); });
//process.stdin.on('readable', function () { var chunk; while ((chunk = process.stdin.read()) !== null) { settings.tunnelws.send(Buffer.from(chunk)); } });
process.stdin.on('end', function () { process.exit(); });
process.stdout.on('resize', function() {
var termSize = null;
if (typeof process.stdout.getWindowSize == 'function') { termSize = process.stdout.getWindowSize(); }
if (termSize != null) { settings.tunnelws.send(JSON.stringify({ ctrlChannel: '102938', type: 'termsize', cols: termSize[0], rows: termSize[1] })); }
});
}
});
}
// Encode an object as a cookie using a key using AES-GCM. (key must be 32 bytes or more) // Encode an object as a cookie using a key using AES-GCM. (key must be 32 bytes or more)
function encodeCookie(o, key) { function encodeCookie(o, key) {
try { try {
@ -1069,6 +1148,7 @@ function encodeCookie(o, key) {
// Generate a random Intel AMT password // 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 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(crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; } function getRandomAmtPassword() { var p; do { p = Buffer.from(crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; }
function getRandomHex(count) { return Buffer.from(crypto.randomBytes(count), 'binary').toString('hex'); }
function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); }; function format(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/{(\d+)}/g, function (match, number) { return typeof args[number] != 'undefined' ? args[number] : match; }); };
function displayDeviceInfo(sysinfo, lastconnect, network) { function displayDeviceInfo(sysinfo, lastconnect, network) {

View File

@ -175,8 +175,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
} }
// Route a command to a target node // Route a command to a target node
function routeCommandToNode(command) { function routeCommandToNode(command, func) {
if (common.validateString(command.nodeid, 8, 128) == false) return false; if (common.validateString(command.nodeid, 8, 128) == false) { if (func) { func(false); } return false; }
var splitnodeid = command.nodeid.split('/'); var splitnodeid = command.nodeid.split('/');
// Check that we are in the same domain and the user has rights over this node. // Check that we are in the same domain and the user has rights over this node.
if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domain.id)) { if ((splitnodeid[0] == 'node') && (splitnodeid[1] == domain.id)) {
@ -201,7 +201,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text
delete command.nodeid; // Remove the nodeid since it's implied delete command.nodeid; // Remove the nodeid since it's implied
try { agent.send(JSON.stringify(command)); } catch (ex) { } try { agent.send(JSON.stringify(command)); } catch (ex) { }
} } else { if (func) { func(false); } }
}); });
} else { } else {
// Check if a peer server is connected to this agent // Check if a peer server is connected to this agent
@ -224,11 +224,11 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
command.remoteaddr = req.clientIp; // User's IP address command.remoteaddr = req.clientIp; // User's IP address
if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text if (typeof domain.desktopprivacybartext == 'string') { command.privacybartext = domain.desktopprivacybartext; } // Privacy bar text
parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid); parent.parent.multiServer.DispatchMessageSingleServer(command, routing.serverid);
} } else { if (func) { func(false); } }
}); });
} else { if (func) { func(false); } return false; }
} }
} } else { if (func) { func(false); } return false; }
}
return true; return true;
} }
@ -391,7 +391,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
var httpport = ((args.aliasport != null) ? args.aliasport : args.port); var httpport = ((args.aliasport != null) ? args.aliasport : args.port);
// Build server information object // Build server information object
var serverinfo = { name: domain.dns ? domain.dns : parent.certificates.CommonName, mpsname: parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: args.mpspass, port: httpport, emailcheck: ((parent.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (args.lanonly != true) && (parent.certificates.CommonName != null) && (parent.certificates.CommonName.indexOf('.') != -1)), domainauth: (domain.auth == 'sspi'), serverTime: Date.now() }; var serverinfo = { domain: domain.id, name: domain.dns ? domain.dns : parent.certificates.CommonName, mpsname: parent.certificates.AmtMpsName, mpsport: mpsport, mpspass: args.mpspass, port: httpport, emailcheck: ((parent.parent.mailserver != null) && (domain.auth != 'sspi') && (domain.auth != 'ldap') && (args.lanonly != true) && (parent.certificates.CommonName != null) && (parent.certificates.CommonName.indexOf('.') != -1)), domainauth: (domain.auth == 'sspi'), serverTime: Date.now() };
serverinfo.languages = parent.renderLanguages; serverinfo.languages = parent.renderLanguages;
serverinfo.tlshash = Buffer.from(parent.webCertificateHashs[domain.id], 'binary').toString('hex').toUpperCase(); // SHA384 of server HTTPS certificate serverinfo.tlshash = Buffer.from(parent.webCertificateHashs[domain.id], 'binary').toString('hex').toUpperCase(); // SHA384 of server HTTPS certificate
if ((parent.parent.config.domains[domain.id].amtacmactivation != null) && (parent.parent.config.domains[domain.id].amtacmactivation.acmmatch != null)) { if ((parent.parent.config.domains[domain.id].amtacmactivation != null) && (parent.parent.config.domains[domain.id].amtacmactivation.acmmatch != null)) {
@ -1207,8 +1207,12 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
} }
} }
// If a response is needed, set a callback function
var func = null;
if (command.responseid != null) { func = function (r) { try { ws.send(JSON.stringify({ action: 'msg', result: r ? 'OK' : 'Unable to route', tag: command.tag, responseid: command.responseid })); } catch (ex) { } } }
// Route this command to a target node // Route this command to a target node
routeCommandToNode(command); routeCommandToNode(command, func);
break; break;
} }
case 'events': case 'events':

File diff suppressed because one or more lines are too long