If an attacker can execute arbitrary code on your servers, your systems are almost certainly going to be compromised. You need to take great care when designing how your web server interacts with the underlying operating system.
Risks
Command injection is a major security lapse, and the last step along the road to complete system takeover. After gaining access, an attacker will attempt to escalate their privileges on the server, install malicious scripts, or make your server part of a botnet to be used at a later date.
Command injection vulnerabilities often occur in older, legacy code, such as CGI scripts.
Protection
If your application calls out to the operating system, you need to be sure command strings are securely constructed, or else you risk having malicious instructions injected by an attacker. This section outlines a few approaches to protecting yourself.
Try to Avoid Command Line Calls Altogether
Modern programming languages have interfaces that permit you to read files, send emails, and perform other operating system functions. Use APIs wherever possible – only use shell commands where absolutely necessary. This will reduce the number of attack vectors in your application, and will also simplify your codebase.
Escape Inputs Correctly
Injection vulnerabilities occur when untrusted input is not sanitized correctly. If you use shell commands, be sure to scrub input values for potentially malicious characters:
; |
& |
| |
` |
Even better, restrict input by testing it against a regular expression of known safe characters. (For example, alphanumeric characters.)
Restrict the Permitted Commands
Try to construct all or most of your shell commands using string literals, rather than user input. Where user input is required, try to allowlist permitted values, or enumerate them in a conditional statement.
Perform Thorough Code Reviews
Check system calls for vulnerabilities as a part of your code review process. Vulnerabilities often creep in over time – make sure your team knows what to look for.
Run with Restricted Permissions
It is a good practice to run your server processes with only the permissions that they require to function – the principle of least privilege. This can help limit the impact of command injection vulnerabilities as a second line of defense.
Make sure each web server process can only access the directories that it needs, and narrow down the directories in which they write or execute files. Consider running the process in a chroot jail if you are running on Unix. This will limit the ability of maliciously injected code to “climb out” of a directory.
Code Samples
The code samples below illustrate how to safely make command-line calls in various languages.
Python
New processes are spawned in python using the modules
popen2
,
os
,
commands
, and
subprocess
.
subprocess
is the preferred API (the others are deprecated and replaced by
it). The subprocess
module has built-in protection against command execution:
from subprocess import call
# An invocation of the call(...) function will ensure only a single
# command is run.
call(["ls", "-l"])
This protection can be disabled – be on the lookout for anything that opens a process in the following manner:
from subprocess import call
# shell=True disables command injection checking.
call("cat " + filename, shell=True)
Ruby
Ruby offers a multitude of ways to make command line calls:
eval "ls -l"
system "ls -l"
`ls -l` # Backticks indicate an OS command to be executed.
Kernel.exec("ls -l")
%x( ls -l )
open("|date") do |cmd|
print cmd.gets
end
If you must use command line calls in your application, be sure to
sanitize inputs using the Shellwords
module:
open("| grep #{Shellwords.escape(pattern)} file") { |pipe|
# ...
}
Java
Since Java is run in a virtual machine, command-line calls are generally
discouraged. They can be executed using the java.lang.Runtime
API, however:
Runtime.getRuntime().exec("ls -l");
The call to exec(...)
will tokenize the input and make sure only a single
command is run. Just be sure not to write your own tokenizer!
.NET
There are a couple of ways to access the command line in C#, though .NET has a very comprehensive set of standard libraries, so you will rarely need to:
System.Diagnostics.Process.Start("CMD.exe", "ls -l");
var process = new ProcessStartInfo();
process.UseShellExecute = true;
process.WorkingDirectory = @"C:\Windows\System32";
process.FileName = @"C:\Windows\System32\cmd.exe";
process.Verb = "runas";
process.Arguments = "/c ls -l";
Process.Start(process);
Node
The usual way of invoking shell commands in Node is using the
child_process
module. This is a wrapper around bin.sh, so concatenating
command strings is risky:
var child_process = require('child_process');
child_process.exec('ls -l ' + input_path, function (err, data) {
console.log(data);
});
Instead, use one of the functions that take arrays as arguments:
child_process.execFile('/bin/ls', ['-l', input_path], function (err, result) {
console.log(result)
});
var ls = child_process.spawn('/bin/ls', ['-l', input_path])
PHP
Making command-line calls in PHP is fairly common, and there are a number of ways to make them:
shell_exec "ls -l"
exec "ls -l"
passthru "ls -l"
system "ls -l"
`ls -l`
Be careful to sanitize the inputs to any of these functions.
Further Reading
- A note about shell commands in Python.
- A guide to shell commands in Ruby.
- Command injection in Ruby.
- Command injection in Node.