Skip to content
This repository has been archived by the owner on Dec 19, 2023. It is now read-only.

Fix command injection vulnerability #2

Merged
merged 1 commit into from
Apr 10, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 52 additions & 55 deletions lib/virtualbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

// @todo use a promise library instead of so many callbacks

var exec = require('child_process').exec,
var execFile = require('child_process').execFile,
host_platform = process.platform,
logging = require('./logging'),
vBoxManageBinary,
Expand All @@ -11,7 +11,9 @@ var exec = require('child_process').exec,
WINDOWS: 'windows',
MAC: 'mac',
LINUX: 'linux'
};
},
allowedBinaries = ['VBoxControl', vBoxManageBinary];



// Host operating system
Expand All @@ -33,14 +35,18 @@ if (/^win/.test(host_platform)) {

}

exec(vBoxManageBinary + ' --version', function(error, stdout, stderr) {
exec(vBoxManageBinary, ['--version'], function(error, stdout, stderr) {
// e.g., "4.3.38r106717" or "5.0.20r106931"
vbox_version = stdout.split(".")[0];
logging.info("Virtualbox version detected as %s", vbox_version);
});

function command(cmd, callback) {
exec(cmd, function(err, stdout, stderr) {
function command(bin, cmd, callback) {
if(!allowedBinaries.includes(bin)) {
throw new Error('Not an allowed binary');
}

execFile(bin, cmd, function(err, stdout, stderr) {

if (!err && stderr && cmd.indexOf("pause") !== -1 && cmd.indexOf("savestate") !== -1) {
err = new Error(stderr);
Expand All @@ -51,26 +57,26 @@ function command(cmd, callback) {
}

function vboxcontrol(cmd, callback) {
command('VBoxControl ' + cmd, callback);
command('VBoxControl', cmd, callback);
}

function vboxmanage(cmd, callback) {
command(vBoxManageBinary + cmd, callback);
command(vBoxManageBinary, cmd, callback);
}

function pause(vmname, callback) {
logging.info('Pausing VM "%s"', vmname);
vboxmanage('controlvm "' + vmname + '" pause', function(error, stdout) {
vboxmanage(['controlvm', vmname, 'pause'], function(error, stdout) {
callback(error);
});
}

function list(callback) {
logging.info('Listing VMs');
vboxmanage('list "runningvms"', function(error, stdout) {
vboxmanage(['list', 'runningvms'], function(error, stdout) {
var _list = {};
var _runningvms = parse_listdata(stdout);
vboxmanage('list "vms"', function(error, full_stdout) {
vboxmanage(['list', 'vms'], function(error, full_stdout) {
var _all = parse_listdata(full_stdout);
var _keys = Object.keys(_all);
for (var _i = 0; _i < _keys.length; _i += 1) {
Expand Down Expand Up @@ -111,29 +117,28 @@ function parse_listdata(raw_data) {

function reset(vmname, callback) {
logging.info('Resetting VM "%s"', vmname);
vboxmanage('controlvm "' + vmname + '" reset', function(error, stdout) {
vboxmanage(['controlvm', vmname, 'reset'], function(error, stdout) {
callback(error);
});
}

function resume(vmname, callback) {
logging.info('Resuming VM "%s"', vmname);
vboxmanage('controlvm "' + vmname + '" resume', function(error, stdout) {
vboxmanage(['controlvm', vmname, 'resume'], function(error, stdout) {
callback(error);
});
}

function start(vmname, use_gui, callback) {
var start_opts = ' --type ';
if ((typeof use_gui) === 'function') {
callback = use_gui;
use_gui = false;
}
start_opts += (use_gui ? 'gui' : 'headless');
var vmType = (use_gui ? 'gui' : 'headless');

logging.info('Starting VM "%s" with options: ', vmname, start_opts);
logging.info('Starting VM "%s" with options: ', vmname, vmType);

vboxmanage('-nologo startvm "' + vmname + '"' + start_opts, function(error, stdout) {
vboxmanage(['-nologo', 'startvm', vmname, '--type', vmType], function(error, stdout) {
if (error && /VBOX_E_INVALID_OBJECT_STATE/.test(error.message)) {
error = undefined;
}
Expand All @@ -143,7 +148,7 @@ function start(vmname, use_gui, callback) {

function stop(vmname, callback) {
logging.info('Stopping VM "%s"', vmname);
vboxmanage('controlvm "' + vmname + '" savestate', function(error, stdout) {
vboxmanage(['controlvm', vmname, 'savestate'], function(error, stdout) {
callback(error);
});
}
Expand All @@ -155,28 +160,28 @@ function savestate(vmname, callback) {

function vmExport(vmname, output, callback) {
logging.info('Exporting VM "%s"', vmname);
vboxmanage('export "' + vmname + '" --output "' + output + '"', function(error, stdout) {
vboxmanage(['export', vmname, '--output', output], function(error, stdout) {
callback(error);
});
}

function poweroff(vmname, callback) {
logging.info('Powering off VM "%s"', vmname);
vboxmanage('controlvm "' + vmname + '" poweroff', function(error, stdout) {
vboxmanage(['controlvm', vmname, 'poweroff'], function(error, stdout) {
callback(error);
});
}

function acpipowerbutton(vmname, callback) {
logging.info('ACPI power button VM "%s"', vmname);
vboxmanage('controlvm "' + vmname + '" acpipowerbutton', function(error, stdout) {
vboxmanage(['controlvm', vmname, 'acpipowerbutton'], function(error, stdout) {
callback(error);
});
}

function acpisleepbutton(vmname, callback) {
logging.info('ACPI sleep button VM "%s"', vmname);
vboxmanage('controlvm "' + vmname + '" acpisleepbutton', function(error, stdout) {
vboxmanage(['controlvm', vmname, 'acpisleepbutton'], function(error, stdout) {
callback(error);
});
}
Expand All @@ -199,18 +204,14 @@ function modify(vname, properties, callback) {
}
}

var cmd = 'modifyvm ' + args.map(function (arg) {
return '"' + arg + '"';
}).join(' ');

vboxmanage(cmd, function (error, stdout) {
vboxmanage(['modifyvm', ...args], function (error, stdout) {
callback(error);
});
}

function snapshotList(vmname, callback) {
logging.info('Listing snapshots for VM "%s"', vmname);
vboxmanage('snapshot "' + vmname + '" list --machinereadable', function(error, stdout) {
vboxmanage(['snapshot', vmname, 'list', '--machinereadable'], function(error, stdout) {

if (error) {
callback(error);
Expand Down Expand Up @@ -255,14 +256,15 @@ function snapshotTake(vmname, name, /*optional*/ description, /*optional*/ live,
live = false;
}

var cmd = 'snapshot ' + JSON.stringify(vmname) + ' take ' + JSON.stringify(name);
var cmd = ['snapshot', vmname, 'take', name];

if(description) {
cmd += ' --description ' + JSON.stringify(description);
cmd.push('--description');
cmd.push(description);
}

if(live === true) {
cmd += ' --live';
cmd.push('--live');
}

vboxmanage(cmd, function(error, stdout) {
Expand All @@ -276,19 +278,16 @@ function snapshotTake(vmname, name, /*optional*/ description, /*optional*/ live,

function snapshotDelete(vmname, uuid, callback) {
logging.info('Deleting snapshot "%s" for VM "%s"', uuid, vmname);
var cmd = 'snapshot ' + JSON.stringify(vmname) + ' delete ' + JSON.stringify(uuid);
vboxmanage(cmd, callback);
vboxmanage(['snapshot', vmname, 'delete', uuid], callback);
}

function snapshotRestore(vmname, uuid, callback) {
logging.info('Restoring snapshot "%s" for VM "%s"', uuid, vmname);
var cmd = 'snapshot ' + JSON.stringify(vmname) + ' restore ' + JSON.stringify(uuid);
vboxmanage(cmd, callback);
vboxmanage(['snapshot', vmname, 'restore', uuid], callback);
}

function isRunning(vmname, callback) {
var cmd = 'list runningvms';
vboxmanage(cmd, function (error, stdout) {
vboxmanage(['list', 'runningvms'], function (error, stdout) {
logging.info('Checking virtual machine "%s" is running or not', vmname);
if (stdout.indexOf(vmname) === -1) {
callback(error, false);
Expand All @@ -308,7 +307,7 @@ function keyboardputscancode(vmname, codes, callback) {
return s;
}).join(' ');
logging.info('Sending VM "%s" keyboard scan codes "%s"', vmname, codeStr);
vboxmanage('controlvm "' + vmname + '" keyboardputscancode ' + codeStr, function(error, stdout) {
vboxmanage(['controlvm', vmname, 'keyboardputscancode', codeStr], function(error, stdout) {
callback(error, stdout);
});
}
Expand All @@ -332,28 +331,29 @@ function vmExec(options, callback) {
guestproperty.os(vm, getOSTypeCb);

function getOSTypeCb(os_type) {
var cmd = 'guestcontrol "' + vm + '"';
var runcmd = ' execute --image ';

if (vbox_version == 5) {
runcmd = ' run ';
}

var cmd = ['guestcontrol', vm];
var runcmd = vbox_version == 5 ? ['run'] : ['execute', '--image'];
cmd.push(runcmd);
switch (os_type) {
case known_OS_types.WINDOWS:
path = path.replace(/\\/g, '\\\\');
cmd += runcmd + ' "cmd.exe" --username ' + username + (password ? ' --password ' + password : '') + ' -- "/c" "' + path + '" "' + params + '"';
cmd.push('cmd.exe', '--username', username);
break;
case known_OS_types.MAC:
cmd += runcmd + ' "/usr/bin/open -a" --username ' + username + (password ? ' --password ' + password : '') + ' -- "/c" "' + path + '" "' + params + '"';
cmd.push('/usr/bin/open', '-a', '--username', username);
break;
case known_OS_types.LINUX:
cmd += runcmd + ' "/bin/sh" --username ' + username + (password ? ' --password ' + password : '') + ' -- "/c" "' + path + '" "' + params + '"';
cmd.push('/bin/sh', '--username', username);
break;
default:
break;
}

if (password) {
cmd.push('--password', password);
}
cmd.push('--', '/c', path, params);

logging.info('Executing command "vboxmanage %s" on VM "%s" detected OS type "%s"', cmd, vm, os_type);

vboxmanage(cmd, function(error, stdout) {
Expand All @@ -367,8 +367,7 @@ function vmKill(options, callback) {
options = options || {};
var vm = options.vm || options.name || options.vmname || options.title,
path = options.path || options.cmd || options.command || options.exec || options.execute || options.run,
image_name = options.image_name || path,
cmd = 'guestcontrol "' + vm + '" process kill';
image_name = options.image_name || path;

guestproperty.os(vm, function(os_type) {
switch (os_type) {
Expand Down Expand Up @@ -404,9 +403,8 @@ var guestproperty = {

guestproperty.os(vm, getOSTypeCallback);

function getOSTypeCallback(os_type) {
var cmd = 'guestproperty get "' + vm + '" ' + key;
vboxmanage(cmd, function(error, stdout) {
function gvboxmanageetOSTypeCallback(os_type) {
vboxmanage(['guestproperty', 'get', vm, key], function(error, stdout) {
if (error) {
throw error;
}
Expand Down Expand Up @@ -445,7 +443,7 @@ var guestproperty = {
}

try {
exec(vBoxManageBinary + 'showvminfo -machinereadable "' + vmname + '"', getOSTypeCallback);
vboxmanage('showvminfo', '--machinereadable', vmname, getOSTypeCallback);
} catch (e) {
logging.info('Could not showvminfo for %s', vmname);
}
Expand All @@ -459,8 +457,7 @@ var extradata = {
key = options.key,
value = options.defaultValue || options.value;

var cmd = 'getextradata "' + vm + '" "' + key + '"';
vboxmanage(cmd, function(error, stdout) {
vboxmanage(['getextradata', vm, key], function(error, stdout) {
if (error) {
callback(error);
return;
Expand Down