Handling backticks in Perl

Command Line Example | Insecure Common Gateway Interface (CGI) Script | Solution: quotemeta | Solution: Piped Open | Solution: Input Validation | Taint Mode | Lengthy Shell Commands

In Perl, backticks `` and the system and exec functions run external programs. Shell escapes should be avoided, as they impose needless security, portability, and maintainability problems. In many cases, a Perl function, or module from the Comprehensive Perl Archive Network (CPAN), can safely replace the backtick, such as using chmod 0755 @files in Perl instead of shelling out to chmod(1). However, if the shell escapes are necessary, or time does not permit rewriting the offending code, steps must be taken to avoid security flaws. Insecure uses of backticks are outlined below, along with replacement code that avoids the security problems. For more information on backticks, consult the `STRING` documentation in perlop.

$ perldoc perlop

The examples below use the echo(1) and ls(1) commands; an attacker would normally use a command that grants elevated access to the system or one that carries out illegal actions, such as the distribution of spam or stealing of customer data. As most programs do not log what commands they run, an audit might never find the breach.

Consult Perl Best Practices and Secure Programming Cookbook for C and C++ for more information on coding and security best practices. Consider selinux, systrace, or other mandatory access controls to either prevent or log the execution of arbitrary commands.

Command Line Example

As written, the following Perl code contains a security flaw.

$ echo a b c
a b c
$ perl -e 'print `echo @ARGV`' a b c
a b c
$ perl -e 'print qx/echo @ARGV/' Hello World
Hello World

Exploits that run an attacker-supplied ls command:

$ perl -e 'print `echo @ARGV`' a b c '`ls`'
a b c CVS index.xml
$ perl -e 'print `echo @ARGV`' a b c '; ls'
a b c
CVS
index.xml

The first exploit runs ls as a sub command; the second appends the ls as a second command. In the first example, the single quotes around `ls` prevent the Unix shell from executing the exploit before it is passed to the script.

Insecure Common Gateway Interface (CGI) Script

The insecure.pl CGI script can be run from the command line or on a private web server.

#!/usr/bin/perl -w
use strict;

use CGI;
my $cgi = CGI->new;
my $user_input = $cgi->param('input');

print $cgi->header('text/plain');
print "Echo returns: ", qx/echo $user_input/;

To test insecure.pl locally, run it on the command line.

$ perl insecure.pl input='`ls`'
Content-Type: text/plain; charset=ISO-8859-1

Echo returns: CVS index.xml insecure.pl

Remote attacks must encode the request. With Perl, URI::Escape and LWP can construct and send the exploit code.

$ perl -MURI::Escape -le 'print uri_escape shift' '`ls`'
%60ls%60
$ lwp-request http://example.org/cgi-bin/insecure.pl?input=%60ls%60
Echo returns: FormMail.pl insecure.pl

Assuming user supplied input must be passed to backtick or system calls, there are several ways to prevent improper commands from being executed. If possible, do not involve user input with external commands. User input could arrive as input to a program, or as an environment variable (for example adjusting PATH). See perlsec for information on sanitizing %ENV.

Solution: quotemeta

Quoting the data may thwart exploits. Use the quotemeta function or equivalent \Q operator. The first command shows how echo now prints the exploit code instead of running it, and the second how the data is quoted.

$ perl -e 'print qx/echo \Q@ARGV/' a b c '; ls'
a b c ; ls
$ perl -le 'print "\Q@ARGV"' a b c '; ls'
a\ b\ c\ \;\ ls

quotemeta and \Q share similar function, if slightly different usage.

$ perl -le 'print quotemeta "@ARGV"' a b c '; ls'
a\ b\ c\ \;\ ls

Multiple levels of shell calls may require multiple levels of escaping. This is another good reason to avoid shell escapes where possible.

Solution: Piped Open

Avoid running commands though the shell. To avoid the shell, use piped opens and pass commands to exec as a list. For more information on piped opens, see perlipc and perlsec.

open my $fh_echo, '-|' or exec 'echo', @ARGV or die "echo failed: $!\n";
print <$fh_echo>;
close $fh_echo;

If only the exit code of a command is needed, call the system function with a list to avoid invoking the shell, then check $? for the return value.

system 'echo', @ARGV;
print "ran okay\n" if 0 == $? >> 8;

Longer commands can use qw to quote a list.

system qw{/bin/ls -l -i}, @ARGV;
print "ran okay\n" if 0 == $? >> 8;

Note the use of single quotes 'some', 'command' instead of backticks: exec and system take lists of strings as an argument. Using backticks would cause Perl to execute the code inside the backticks, then pass the output of that code to the function call. This executes commands twice, as seen in this contrived example:

$ perl -e 'exec `echo echo something`'
something

  1. perl runs the Perl expression specified by the -e option.
  2. Perl passes `echo echo something` to the shell.
  3. The shell echo command returns echo something to Perl.
  4. exec is then invoked with echo something.
  5. exec replaces the Perl process with echo, which prints something.

Therefore, only use backticks when running a shell program (though avoid this where possible). When invoking exec and system, never use backticks, and instead pass the command to run as a list of strings.

Solution: Input Validation

Input validation ensures the content and length of input data meet specific requirements. For instance, arguments to echo could be limited to spaces and word characters and be no longer than 32 characters in length.

my $args = "@ARGV";
if (length $args > 32 or $args =~ m/[^\w ]/) {
die "error: invalid command line data\n";
}
print qx/echo \Q@ARGV/;

The [^\w ] Perl regular expression matches if the $args variable contains characters not in \w or a space. This is a proper “deny that which is not explicitly permitted” policy.

The CPAN lists many input checking modules. Search for taint or valid in module names. Regular expression libraries such as Regexp::Common also facilitate checks against known data types, such as zip codes or Internet addresses.

Taint Mode

For detailed information on Perl’s taint mode, see perlsec. Enabling taint mode forces validation (untainting) of environment variables and user-supplied data. The shell call to echo fails due to the tainted contents of @ARGV:

$ perl -T -e 'print `echo @ARGV`' a b c
Insecure dependency in `` while running with -T switch at -e line 1.

Taint mode does not solve all problems: it must be used in conjunction with other security measures. Even with taint mode enabled, sloppy coding will still result in security flaws. A blanket untaint with m/(.*)/ removes the taint, but allows a security flaw through.

$ perl -T -e '"@ARGV" =~ m/(.*)/; $args = $1; \
$ENV{PATH} = "/bin"; print `echo $args`' a b c '; ls'

a b c
CVS
index.xml
insecure.pl

Lengthy Shell Commands

Embedding multi-line shell commands in Perl is possible, though I avoid the use of shell commands in my scripts—debugging and unit testing one language is hard enough, let alone mixing random fragments of another into the first. However, if necessary, a here doc allows shell scripts longer than a single line to be embedded in a Perl script. Search for <<EOF or here doc in perlop for more details.

#!/usr/bin/perl
use strict;
use warnings;

my $result = <<`SHELL_IN_PERL_IS_BAD`;
echo foo
echo bar
echo zot
SHELL_IN_PERL_IS_BAD

print $result;