Skip to main content

    Command injection prevention for JavaScript

    This is a command injection prevention cheat sheet by Semgrep, Inc. It contains code patterns of potential ways to run an OS command in an application. Instead of scrutinizing code for exploitable vulnerabilities, the recommendations in this cheat sheet pave a safe road for developers that mitigate the possibility of command injection in your code. By following these recommendations, you can be reasonably sure your code is free of command injection.

    Check your project using Semgrep

    The following command runs an optimized set of rules for your project:

    semgrep --config p/default

    1. Running an OS command

    1.A. Spawning a shell with exec function

    The exec() function executes shell commands. It spawns a shell and then executes the command within the spawned shell, buffering any generated output. Never pass unsanitized user input to this function. Any input containing shell metacharacters can be used to trigger the execution of arbitrary commands.

    Example:

    const {exec} = require('child_process')

    const userInput = "$(dangerous command)" // Value supplied by user input

    // Vunerable
    exec(`cat *.js ${userInput}`, (error, stdout, stderr) => {
    console.log(stdout)
    })

    References

    Mitigation

    • Always try to use internal JavaScript APIs (if they exist) instead of running an OS command. In other words, use internal language features instead of invoking commands that can be manipulated by a malicious actor.
    • Don’t pass user-controlled input or use an allowlist for inputs.
    • If it is not possible, use an array with a sequence of program arguments instead of a single string.
    • Avoid non-literal values for the command string. Strip everything except alphanumeric characters from an input provided for the command string and arguments.

    Semgrep rule

    javascript.lang.security.detect-child-process.detect-child-process

    1.B. Spawning a process with spawn function

    The spawn() and spawnSync() functions spawn a new process using the command with a list of arguments. This allows spawning any programs or running shell processes with arbitrary arguments, which can result in a command injection vulnerability.

    Example:

    const {spawn} = require('child_process')

    // Not vulnerable
    const userInput = '' // value supplied by user input
    spawn('cat', ['*.js', userInput])

    // Vulnerable
    const userInput = 'pwn-command' // value supplied by user input
    spawn(userInput, ['-a', '-b'])

    // Vulnerable
    const userInput = 'echo 1 | cat /etc/passwd' // value supplied by user input
    spawn("sh", ["-c", userInput])

    References

    child_process module documentation

    Mitigation

    • Always try to use internal JavaScript APIs (if they exist) instead of running an OS command. In other words, use internal language features instead of invoking commands that can be manipulated by a malicious actor.
    • Don’t pass user-controlled input or use an allowlist for inputs.
    • Do not include command arguments in a command string, use parameterization instead. For example:
      Use:
      spawn("/path/to/myCommand", ["myArg1", inputValue])
      Instead of:
      spawn("bash", ["-c", "myCommand myArg1 " + inputValue])
    • Define a list of allowed arguments.
    • Avoid non-literal values for the command string. Strip everything except alphanumeric characters from an input provided for the command string and arguments.

    Semgrep rule

    javascript.lang.security.detect-child-process.detect-child-process

    Not finding what you need in this doc? Ask questions in our Community Slack group, or see Support for other ways to get help.