Skip to content

Command Injection

What does this mean ?

The purpose of a command injection attack is to execute arbitrary instructions on the host operating system via a susceptible application. When an application sends dangerous user-supplied data (forms, cookies, HTTP headers, etc.) to a system shell, command injection attacks are conceivable. In this attack, the attacker-supplied operating system instructions are often performed with the susceptible application's privileges. Inadequate input validation makes command injection attacks viable.

What can happen ?

This flaw allows an attacker to execute arbitrary operating system (OS) instructions on a server that is executing an application, often compromising the program and all of its data. An attacker can frequently use an operating system command injection vulnerability to compromise other portions of the hosting infrastructure, leveraging trust connections to pivot the assault to other systems inside the company.

Recommendation

The most effective technique to avoid OS command injection vulnerabilities is to never use application-layer code to call out to OS commands. In almost every scenario, there are safer platform APIs that can be used to achieve the desired functionality. If it is determined that calling out to OS commands with user-supplied input is inevitable, then robust input validation must be applied. Here are some instances of successful validation: Validation against a whitelist of acceptable values. Ensures that the input is a number. Ensures that the input only contains alphanumeric characters and no other syntax or whitespace. Attempting to sanitize input by escaping shell metacharacters is never a good idea. In fact, this is far too error-prone and easily circumvented by a smart attacker.

Sample Code

Vulnerable :

var p = new Process();
p.StartInfo.FileName = "exportLegacy.exe";
p.StartInfo.Arguments = " -user " + input + " -role user";
p.Start();

Non Vulnerable :

Regex rgx = new Regex(@"^[a-zA-Z0-9]+$");
if(rgx.IsMatch(input))
{
    var p = new Process();
    p.StartInfo.FileName = "exportLegacy.exe";
    p.StartInfo.Arguments = " -user " + input + " -role user";
    p.Start();
}

Vulnerable :

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;

public void runUnsafe(HttpServletRequest request) throws IOException {
  String cmd = request.getParameter("command");
  String arg = request.getParameter("arg");

  Runtime.getRuntime().exec(cmd+" "+arg); // Noncompliant
}

Non Vulnerable :

import java.io.IOException;
import javax.servlet.http.HttpServletRequest;

public void runUnsafe(HttpServletRequest request) throws IOException {
  String cmd = request.getParameter("command");
  String arg = request.getParameter("arg");

  if(cmd.equals("/usr/bin/ls") || cmd.equals("/usr/bin/cat"))
  {
      // only ls or cat command are authorized
      String cmdarray[] =  new String[] { cmd, arg };
      Runtime.getRuntime().exec(cmdarray); // Compliant
  }
}
MySecurityManager sm = new MySecurityManager();
System.setSecurityManager(sm);

Vulnerable :

$binary = $_GET["binary"];

// If the value "/sbin/shutdown" is passed as binary and the web server is running as root,
// then the machine running the web server will be shut down and become unavailable for future requests

exec( $binary ); // Noncompliant

Non Vulnerable :

$binary = $_GET["binary"];

// Restrict to binaries within the current working directory whose name only contains letters
$pattern = "[a-zA-Z]++";
if ( preg_match($pattern, $binary) ) {
  exec( $binary ); // Compliant
}

Vulnerable :

app.get('/',  (req, res) => {
  child_process.exec(
    'gzip ' + req.query.file_path,
    function (err, data) {
      console.log('err: ', err)
      console.log('data: ', data);
    });
  res.send('Hello World!')
})

Non Vulnerable :

app.get('/', function (req, res) {
  child_process.execFile(
    'gzip ' + req.query.file_path,
    function (err, data) {
      console.log('err: ', err)
      console.log('data: ', data);
    });
  res.send('Hello World!')
})

Vulnerable :

let cp = require('child_process');

(req, res) => {
  const cmd = 'ls '+req.query.arg;

  const out = cp.execSync(cmd); // Vulnerable
}

Non Vulnerable :

let cp = require('child_process');

(req, res) => {
  const out = cp.execFileSync("ls", [req.query.arg]); // Non Vulnerable
}

Vulnerable :

bin, err := exec.LookPath("sh")
if err != nil {
  panic(err)
}
env := os.Environ()
args := []string{"sh", "-c", req.FormValue("name")}
execErr := syscall.Exec(bin, args, env)
if execErr != nil {
    panic(execErr)
}

Non Vulnerable :

bin, err := exec.LookPath("echo")
if err != nil {
  panic(err)
}
env := os.Environ()
args := []string{"echo", req.FormValue("name")}
execErr := syscall.Exec(bin, args, env)
if execErr != nil {
    panic(execErr)
}

Vulnerable :

class Resolver < ActiveRecord::Base
  def self.lookup(hostname)
    system("nslookup #{hostname}")
  end
end

Non Vulnerable :

class Resolver < ActiveRecord::Base
  def self.lookup(hostname)
    system("nslookup", hostname)
  end
end

References