Methods to securely invoke system commands in node. js (to avoid injecting security holes)

  • 2020-03-30 04:29:54
  • OfStack

In this article, we'll learn how to call system commands correctly using node. js to avoid the common command-line injection vulnerability.

The method we often use to invoke the command is the simplest child_process.exec. It has a very simple usage pattern; By passing in a string command and passing an error or command processing back to the callback function.

Here is a very typical example of how you can invoke a system command through child_process.exec.


child_process.exec('ls', function (err, data) {
    console.log(data);
});

But what happens when you need to add some user-entered arguments to the command you're calling? The obvious solution is to string the user input directly to your command. However, my years of experience have taught me that when you send concatenated strings from one system to another, something will go wrong.


var path = "user input";
child_process.exec('ls -l ' + path, function (err, data) {
    console.log(data);
});

Why do connection strings go wrong?

Well, because under the child_process.exec engine, "/bin/sh" will be called. Not the target program. The sent command is simply passed to a new "/bin/sh 'process to execute the shell. The name child_process.exec is somewhat misleading - this is a bash interpreter, not a program to start. This means that all shell characters can have devastating consequences if the parameters entered by the user are executed directly.


[pid 25170] execve("/bin/sh", ["/bin/sh", "-c", "ls -l user input"], []

For example, an attacker could use a semicolon ";" To end the command and start a new call, they can use backquotes or $() to run the subcommand. There is also a lot of potential abuse.

So what's the right way to invoke it?

ExecFile/spawn

Things like spawn and execFile take an extra array argument, not one that can execute other commands in a shell environment, and don't run extra commands.

Let's take a look at the previous examples using execFile and spawn to see how system calls differ and why they're not easily injected by commands.

Child_process. ExecFile


var child_process = require('child_process'); var path = "."
child_process.execFile('/bin/ls', ['-l', path], function (err, result) {
    console.log(result)
});

Running system call

[pid 25565] execve("/bin/ls", ["/bin/ls", "-l", "."], []

Child_process. Spawn

The example with spawn substitution is similar.


var child_process = require('child_process'); var path = "."
var ls = child_process.spawn('/bin/ls', ['-l', path])
ls.stdout.on('data', function (data) {
    console.log(data.toString());
});

Running system call


[pid 26883] execve("/bin/ls", ["/bin/ls", "-l", "."], [

When we use spawn or execfile, our goal is to execute only one command (argument). This means that the user cannot run the injected command because /bin/ls does not know how to handle backquotes or pipes or; . Its /bin/bash is going to explain the arguments to those commands. It is similar to using passing parameters into an SQL query (parameter), if you are familiar with it.

One caveat: using spawn or execFile isn't always safe. For example, running /bin/find and passing in user input parameters could still cause the system to be compromised. The find command has options that allow you to read/write arbitrary files.

So, here are some guidelines for running node. js system commands:

Avoid using child_process.exec, especially if you need to include parameters entered by the user.
Try to avoid having the user pass in arguments, and using options is better than having the user enter a string directly.
If you must allow the user to enter parameters, consult the parameters of the command extensively, determine which options are safe, and create a white list.


Related articles: