Running an external program

From Security Wiki
Jump to navigationJump to search

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.