Running an external program
Contents
Running an external program
Your web application needs to execute a shell command. Now what?
Goals
- Learn to safely execute an external program so that user input does not alter the command line being executed
Motivation
Web applications sometimes need to invoke a shell command to process user input. Very often, the user input is incorporated into the command line in a manner that gives an attacker limited or full access to the underlying host as whatever user the web server is running as.
Concerns
In this document, we're concerned with preventing attackers from gaining access to a host through the abuse of shell metacharacters in user input inserted into a command line.
Causes and Conseqhences
An attacker passes a carefully constructed string into a form submission and the web application inserts the attacker's string into a command line and executes the command using system(), exec(), the backtick operator, or similar language construct. The carefully constructed string gives the attacker the ability to run any command on the system, or some other, more limited privilege.
An attacker can perform actions on the web server's host as if they had a shell account.
Prevention
Sanitize user input
See the page on Safe handling of user input. Always do this regardless of what method you use below.
Avoid inserting user input into a command line
Most programs have the ability to accept command line flags, configuration and switches through other means, such as from stdin or a file. Use this method whenever possible!
Pass arguments separately
Some languages allow arguments to a shell command to be passed separately from its arguments. In perl for example, you can provide the program's arguments separately from the program.
$cmd = "foo"; @args = ($cmd, "-a", "-i", "$id"); system($cmd,@args);
insted of
$cmd = "foo -a -i $id"; system($cmd);
Pass arguments separately part 2
... and some languages do not. PHP, for example, does not allow passing arguments separately from the program.
In some of these caes you can use the utility xargs to read command line arguments from stdin. Here's a simple scriot that prints out its arguments:
$ ./showargs 1 2 3 "a string" 4 Arg: 1 Arg: 2 Arg: 3 Arg: a string Arg: 4
This is how to achieve the same result using xargs to read the arguments from stdin:
$ xargs ./showargs 1 2 3 "a string" 4<enter><ctrl-d> Arg: 1 Arg: 2 Arg: 3 Arg: a string Arg: 4
And xargs won't interpret shell metacharacters that might have gotten by the escaping mechanism or filter:
$ xargs ./showargs 1 2 >test.file<enter><ctrl-d> Arg: 1 Arg: 2 Arg: >test.file $ ls -la test.file ls: cannot access test.file: No such file or directory $ xargs ./showargs 1 2 `cat /etc/passwd > test.file`<enter><ctrl-d> Arg: 1 Arg: 2 Arg: `cat Arg: /etc/passwd Arg: > Arg: test.file` $ ls -la test.file ls: cannot access test.file: No such file or directory
Last option: escape
If all else fails, you can try to escape the shell metacharacters for each user-supplied argument. PHP has a few tools for this: escapeshellcmd() and escapeshellarg(). If you must go this route, try to use language-provided functions for this purpose rather than write your own. The ones built in to the language are well-tested and thus less likely to have bugs. Hint: A single regex substitution is most likely not sufficient.