Node.js: Sanitize untrusted user input for exec() - javascript

Small example, reduced from a REST API node.js app:
const { exec } = require('child_process');
var userInput = 'untrusted source';
var cmd = `/bin/echo "${userInput}"`;
exec(cmd, function(err, stdout, stderr) {
console.log('echo: ' + stdout);
});
Assuming the userInput is from an untrusted source, what needs to be done avoid any vulnerability? For example, the quoted "${userInput}" parameter for echo avoids input 'evil spirit; rm -rf /' from causing damage. What else needs to be done to stay safe?
Update: The objective is to make a few existing shell scripts/commands in the file system available via a REST API on the intranet.

Based on the official Node.js child_process doc at https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options it is (obviously) unsafe to use user input in shell scripts without sanitizing it:
If the shell option is enabled, do not pass unsanitized user input to this function. Any input containing shell metacharacters may be used to trigger arbitrary command execution.
So, here is the example stated in my question, rewritten in a safe way using spawn instead of exec:
const { spawn } = require('child_process');
var userInput = 'untrusted source';
var args = [ userInput ];
var cmd = '/bin/echo';
var subprocess = spawn(cmd, args);
var stderr = '';
var stdout = '';
subprocess.stdout.on('data', function(data) {
stdout += data;
});
subprocess.stderr.on('data', function(data) {
stderr += data;
});
subprocess.on('close', function(exitCode) {
console.log('echo: ' + stdout);
});
This is a simplified code snippet of a CLI wrapper Node.js app that make existing commands and shell scripts on an internal network available in a secure way via a REST API: https://github.com/peterthoeny/rest-cli-io

Related

Execute PowerShell script in Node.js

I create register app with node.js and Express.js
So it has name form and password form and submit button.
I want to make it, When I clicked the submit button, run the powershell script. This script means, add local windows user and set the password.
In PowerShell this script works well. And this powershell script should be run as administrator.
$PASSWORD= ConvertTo-SecureString –AsPlainText -Force -String kamisama123##
New-LocalUser -Name "gohan" -Description "sleepy" -Password $PASSWORD
After this command the local user is created.
I use node-powershell package in my code. This code means When I submit the my information and run the PowerShell script and add information in to mongodb
router.post(`/submit`, async function (req, res) {
let name = req.body.name;
let password = req.body.password;
let passwordCheck = req.body.passwordCheck;
let company = req.body.company;
let registerStatus = false;
let executives = req.body.executives;
let registerDate = Date.now();
let ps = new Shell();
let cmd = new PSCommand(`$PASSWORD= ConvertTo-SecureString ?AsPlainText -Force -String ${password}`)
let script = new PSCommand(`New-LocalUser -Name "${name}" -FullName "${name}" -Description "${name}" -PasswordNeverExpires -Password $PASSWORD`)
ps.addCommand(cmd);
ps.addCommand(script);
ㅡㅛ
try {
if (password !== passwordCheck) {
res.redirect('/')
} else {
let encodedPassword = Base64.encode(password);
await User.create({
name,
password: encodedPassword,
company,
registerStatus,
executives,
registerDate
}, (err, result) => {
if (err) throw err;
})
res.redirect(`/success/${name}`)
}
} catch (err) {
throw err;
}
})
But the error throws
(node:21596) UnhandledPromiseRejectionWarning: TypeError: Shell is not a constructor
I don't know where the error comes from.
Constructor is a term from object-oriented programming (OOP), every object in OOP has a constructor function.
For the shell object the constructor can not be empty (shell() has empty brackets)
Normally the constructor of shell has two arguments: execution policy and noProfile
let ps = new shell({
executionPolicy: 'Bypass',
noProfile: true
});
https://rannn505.gitbook.io/node-powershell/start
https://www.jeansnyman.com/posts/executing-powershell-commands-in-a-nodejs-api/
In How to execute Powershell script function with arguments using Node JS? is possibly a solution for this issue. The powershell script has to be wrapped in a promise in nodejs (and possibly be dot-sourced) :
How to execute Powershell script function with arguments using Node JS?
Powershell and potentially also node.js implements the OOP (object-oriented programming) design paradigms and semantics i.e. when writing object-oriented code each class has to have a constructor (new()) and a destructor (remove...) and it should have get() and set() methods to access (read and write) the fields (or attributes) of a class. In ps this is straighlty implemented
It is also possible to use object-oriented design patterns in PS https://dfinke.github.io/powershell,%20design%20patterns/2018/04/13/PowerShell-And-Design-Patterns.html

How can I execute a console function but from the browser in Node.JS?

I'm trying to execute this function but in the terminal with Node.JS
var WebTorrent = require('webtorrent')
var client = new WebTorrent()
var magnetURI = 'magnet: ...'
client.add(magnetURI, { path: '/path/to/folder' }, function (torrent) {
torrent.on('done', function () {
console.log('torrent download finished')
})
})
I mean, for example, create an <button> tag, and when is clicked,
that the previous function be executed in the nodejs console, not in the browser console.
EXTRA:
I'm executing this two files:
app.js
let http = require('http');
let fs = require('fs');
let handleRequest = (request, response) => {
response.writeHead(200, {
'Content-Type': 'text/html'
});
fs.readFile('./index.html', null, function (error, data) {
if (error) {
response.writeHead(404);
respone.write('Whoops! File not found!');
} else {
response.write(data);
}
response.end();
});
};
http.createServer(handleRequest).listen(8000);
And
index.html that contains the <button> tag but does nothing.
Client(browser) and server are two different entities, when client is browser the only way to communicate is through HTTP protocol, in simple terms use internet.
Now browser understand only it's own kind of javascript, more precisely ECMA but not nodejs. So the following code could not be executed in browser
var WebTorrent = require('webtorrent')
var client = new WebTorrent()
Hence I would assume it is running on server which your machine and hence console.log will print to your terminal.
To run it on browser, I assume you will have to code it differently, either you will have to use browserify and analyze the client side script OR code only in client side with below libaray :
<script src="webtorrent.min.js"></script>
For more details refer, complete web page example at https://github.com/webtorrent/webtorrent/blob/master/docs/get-started.md

How to connect node.js app with python script?

I've node app in Meteor.js and short python script using Pafy.
import pafy
url = "https://www.youtube.com/watch?v=AVQpGI6Tq0o"
video = pafy.new(url)
allstreams = video.allstreams
for s in allstreams:
print(s.mediatype, s.extension, s.quality, s.get_filesize(), s.url)
What's the most effective way of connecting them so python script get url from node.js app and return back output to node.js? Would it be better to code it all in Python instead of Meteor.js?
Well, there are plenty of ways to do this, it depends on your requirements.
Some options could be:
Just use stdin/stdout and a child process. In this case, you just need to get your Python script to read the URL from stdin, and output the result to stdout, then execute the script from Node, maybe using child_process.spawn. This is I think the simplest way.
Run the Python part as a server, let's say HTTP, though it could be anything as long as you can send a request and get a response. When you need the data from Node, you just send an HTTP request to your Python server which will return you the data you need in the response.
In both cases, you should return the data in a format that can be parsed easily, otherwise you are going to have to write extra (and useless) logic just to get the data back. Using JSON for such things is quite common and very easy.
For example, to have your program reading stdin and writing JSON to stdout, you could change your script in the following way (input() is for Python 3, use raw_input() if you are using Python 2)
import pafy
import json
url = input()
video = pafy.new(url)
data = []
allstreams = video.allstreams
for s in allstreams:
data.append({
'mediatype': s.mediatype,
'extension': s.extension,
'quality': s.quality,
'filesize': s.get_filesize(),
'url': s.url
})
result = json.dumps(data)
print(result)
Here is a very short example in NodeJS using the Python script
var spawn = require('child_process').spawn;
var child = spawn('python', ['my_script.py']);
child.stdout.on('data', function (data) {
var parsedData = JSON.parse(data.toString());
console.log(parsedData);
});
child.on('close', function (code) {
if (code !== 0) {
console.log('an error has occurred');
}
});
child.stdin.write('https://www.youtube.com/watch?v=AVQpGI6Tq0o');
child.stdin.end();

Calling shell command form javascript form browser

I am trying to run some command on the client system through server. I know server has lots of security issues while executing server commands, is there any way to run command form browser.
I have following commands in nodejs but, i need this to run form the browser in clients system.
same as in this question but form html page.
node.js shell command execution
function run_cmd(cmd, args, callBack ) {
var spawn = require('child_process').spawn;
var child = spawn(cmd, args);
var resp = "";
child.stdout.on('data', function (buffer) { resp += buffer.toString() });
child.stdout.on('end', function() { callBack (resp) });
}
Usage:
run_cmd( "ls", ["-l"], function(text) { console.log (text) });
No, you may not execute arbitrary shell/console commands through a browser.
The security implications for this would be gigantic. You wouldn't want someone to execute:
run_cmd( "rm", ["-rf *"], function(text) { console.log ("lol") });
Through your browser. Not even if you could explicitly trust it.

Full Integration Testing for NodeJS and the Client Side with Yeoman and Mocha

I got awesome client side tests that I run with Yeoman. Yeoman compiles my CoffeeScript, opens up the test page in a server, visit it with PhantomJS and pass all the tests results to the command line. The process is pretty hacky, the test results are passed via alert() messages to the Phantom process which creates a temporary file and fills it with the messages as JSON. Yeoman (well, Grunt) loops over the temporary file, parses the tests and displays them in the command line.
The reason I explained the process is that I want to add a few things to it. I got server side tests as well. They use mocha and supertest to check the API endpoints and a Redis client to make sure the database state is as expected. But I want to merge those two test suites!
I don't want to write client side mock response for the server calls. I don't want to send the server mock data. Somewhere along the way I'll change the server or the client and the test will not fail. I want to do a real integration testing. So, whenever a test finishes in the client side I want a hook to run a relevant test on the server side (checking db state, session state, moving to a different test page).
Are there any solutions to this? Or, altenatively, where do I start hacking on Yeoman / Grunt / grunt-mocha to make this work?
I think the Phantom Handlers in grunt-mocha is a good place to start:
// Handle methods passed from PhantomJS, including Mocha hooks.
var phantomHandlers = {
// Mocha hooks.
suiteStart: function(name) {
unfinished[name] = true;
currentModule = name;
},
suiteDone: function(name, failed, passed, total) {
delete unfinished[name];
},
testStart: function(name) {
currentTest = (currentModule ? currentModule + ' - ' : '') + name;
verbose.write(currentTest + '...');
},
testFail: function(name, result) {
result.testName = currentTest;
failedAssertions.push(result);
},
testDone: function(title, state) {
// Log errors if necessary, otherwise success.
if (state == 'failed') {
// list assertions
if (option('verbose')) {
log.error();
logFailedAssertions();
} else {
log.write('F'.red);
}
} else {
verbose.ok().or.write('.');
}
},
done: function(failed, passed, total, duration) {
var nDuration = parseFloat(duration) || 0;
status.failed += failed;
status.passed += passed;
status.total += total;
status.duration += Math.round(nDuration*100)/100;
// Print assertion errors here, if verbose mode is disabled.
if (!option('verbose')) {
if (failed > 0) {
log.writeln();
logFailedAssertions();
} else {
log.ok();
}
}
},
// Error handlers.
done_fail: function(url) {
verbose.write('Running PhantomJS...').or.write('...');
log.error();
grunt.warn('PhantomJS unable to load "' + url + '" URI.', 90);
},
done_timeout: function() {
log.writeln();
grunt.warn('PhantomJS timed out, possibly due to a missing Mocha run() call.', 90);
},
// console.log pass-through.
// console: console.log.bind(console),
// Debugging messages.
debug: log.debug.bind(log, 'phantomjs')
};
Thanks! There will be a bounty on this.
I don't know about Yeoman - I haven't tried it yet - but I got the rest of the puzzle running. I believe you will figure out the rest.
Why Doing Integration Tests?
In your question you were talking about the situation when you have both client-side tests and server-side tests running with mocks. I assume that for some reason you can't get both test sets running with the same mocks. Otherwise, if you changed the mocks on client-side your server-side tests would fail because they would get the broken mock data.
What you need are the integration tests so when you run some client-side code in your headless browser your server-side code would also run. Moreover, simply running your server-side and client-side code is not enough, you also want to be able to put assertions on both sides, don't you?
Integration Tests with Node and PhantomJS
Most of the examples of integration tests that I found online either use Selenium or Zombie.js. The former is a big Java-based framework to drive real browsers while the later is a simple wrapper around jsdom. I assume you're hesitant to use either of those and would prefer PhantomJS. The tricky part, of course, is to get that running from your Node app. And I got just that.
There are two node modules to drive PhantomJS:
phantom
node-phantom
Unfortunately, both projects seem abandoned by their authors and other community members fork them and adapt to their needs. That means that both projects got forked numerous times and all forks are barely running. The API is almost non-existent. I got my tests running with one of the phantom forks (Thank you, Seb Vincent). Here's a simple app:
'use strict';
var express = require('express');
var app = express();
app.APP = {}; // we'll use it to check the state of the server in our tests
app.configure(function () {
app.use(express.static(__dirname + '/public'));
});
app.get('/user/:name', function (req, res) {
var data = app.APP.data = {
name: req.params.name,
secret: req.query.secret
};
res.send(data);
});
module.exports = app;
app.listen(3000);
})();
It listens for request to /user and returns path parameter name and query parameter secret. Here's the page where I call the server:
window.APP = {};
(function () {
'use strict';
var name = 'Alex', secret ='Secret';
var xhr = new XMLHttpRequest();
xhr.open('get', '/user/' + name + '?secret=' + secret);
xhr.onload = function (e) {
APP.result = JSON.parse(xhr.responseText);
};
xhr.send();
})();
And here's a simple test:
describe('Simple user lookup', function () {
'use strict';
var browser, server;
before(function (done) {
// get our browser and server up and running
phantom.create(function (ph) {
ph.createPage(function (tab) {
browser = tab;
server = require('../app');
server.listen(3000, function () {
done();
});
});
});
});
it('should return data back', function (done) {
browser.open('http://localhost:3000/app.html', function (status) {
setTimeout(function () {
browser.evaluate(function inBrowser() {
// this will be executed on a client-side
return window.APP.result;
}, function fromBrowser(result) {
// server-side asserts
expect(server.APP.data.name).to.equal('Alex');
expect(server.APP.data.secret).to.equal('Secret');
// client-side asserts
expect(result.name).to.equal('Alex');
expect(result.secret).to.equal('Secret');
done();
});
}, 1000); // give time for xhr to run
});
});
});
As you can see I have to poll the server inside the timeout. That's because all the phantom bindings are incomplete and too limiting. As you can see I'm able to check both client state and server state in a single test.
Run your tests with Mocha: mocha -t 2s You'll probably need to increase the default timeout setting for more evolved tests to run.
So, as you can see the whole thing is doable. Here's the repo with complete example.

Categories

Resources