Up to Table of Contents | ||
Back to CGI Scripts | Forward to Server Logs and Privacy |
$date = `/bin/date`;
You can open up a pipe to a program:
open (SORT, " | /usr/bin/sort | /usr/bin/uniq");You can invoke an external program and wait for it to return with system():
system "/usr/bin/sort < foo.in";or you can invoke an external program and never return with exec():
exec "/usr/bin/sort < foo.in";All of these constructions can be risky if they involve user input that may contain shell metacharacters. For system() and exec(), there's a somewhat obscure syntactical feature that allows you to call external programs directly rather than going through a shell. If you pass the arguments to the external program, not in one long string, but as separate members in a list, then Perl will not go through the shell and shell metacharacters will have no unwanted side effects. For example:
system "/usr/bin/sort","foo.in";You can take advantage of this feature to open up a pipe without going through a shell. By calling open on the magic character sequence
|-
, you fork a copy of Perl and open a pipe to the copy.
The child copy can then exec another program using the argument list
variant of exec().
my $result = open (SORT,"|-"); die "Couldn't open pipe to subprocess" unless defined($result); exec "/usr/bin/sort",$uservariable or die "Couldn't exec sort" if $result == 0; for my $line (@lines) { print SORT $line,"\n"; } close SORT;The initial call to open() tries to fork a copy of Perl. If the call fails it returns an undefined value and the script immediately dies (you might want to do something more sophisticated, such as sending an HTML error message to the user). Otherwise, the result will return zero to the child process, and the child's process ID to the parent. The child process checks the result value, and immediately attempts to exec the sort program. If something fails at this point, the child quits.
The parent process can then print to the SORT filehandle in the normal way.
To read from a pipe without opening up a shell, you can do something
similar with the sequence -|
:
$result = open(GREP,"-|"); die "Couldn't open pipe to subprocess" unless defined($result); exec "/usr/bin/grep",'-i',$userpattern,$filename or die "Couldn't exec grep" if $result == 0; while (<GREP>) { print "match: $_"; } close GREP;These are the forms of open() you should use whenever you would otherwise perform a piped open to a command.
An even more obscure feature allows you to call an external program and lie to it about its name. This is useful for calling programs that behave differently depending on the name by which they were invoked.
The syntax is
system $real_name "fake_name","argument1","argument2"For example:
$shell = "/bin/sh"This invokes the shell using the name "-sh", forcing it to behave interactively. Note that the real name of the program must be stored in a variable, and that there's no comma between the variable holding the real name and the start of the argument list.
system $shell "-sh","-norc"
There's also a more compact syntax for this construction:
system { "/bin/sh" } "-sh","-norc"
You turn on taint checks in version 4 of Perl by using a special version of the interpreter named "taintperl":
#!/usr/local/bin/taintperlIn version 5 of perl, pass the -T flag to the interpreter:
#!/usr/local/bin/perl -TSee below for how to "untaint" a variable.
See Gunther Birznieks' CGI/Perl Taint Mode FAQ for a full discussion of taint mode.
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';Adjust this as necessary for the list of directories you want searched. It's not a good idea to include the current directory (".") in the path.
$mail_address=~/(\S+)\@([\w.-]+)/; $untainted_address = "$1\@$2";This pattern match accepts e-mail addresses of the form "who@where" where "where" looks like a domain name, and "who" consists of one or more non-whitespace characters. Note that this regular expression will not remove shell meta-characters from the e-mail address. This is because it is perfectly valid for e-mail addresses to contain such characters, as in:
fred&barney@bedrock.comJust because you have untainted a variable doesn't mean that it is now safe to pass it to a shell. E-mail addresses are the perfect examples of this. The taint checks are there in order to force you to recognize when a variable is potentially dangerous. Use the techniques described in Q44 to avoid passing dangerous variables to the shell.
$foo=~/$user_variable/
is unsafe?foreach (@files) {Now, however, Perl will ignore any changes you make to the user variable, making this sort of loop fail:
m/$user_pattern/o;
}
foreach $user_pattern (@user_patterns) { foreach (@files) { print if m/$user_pattern/o; } }To get around this problem Perl programmers often use this sort of trick:
foreach $user_pattern (@user_patterns) { eval "foreach (\@files) { print if m/$user_pattern/o; }"; }The problem here is that the eval() statement involves a user-supplied variable. Unless this variable is checked carefully, the eval() statement can be tricked into executing arbitrary Perl code. (For example of what can happen, consider what the eval statement does if the user passes in this pattern:
"/; system 'rm *'; /"
The taint checks described above will catch this potential problem. Your alternatives include using the unoptimized form of the pattern matching operation, or carefully untainting user-supplied patterns. In Perl5, a useful trick is to use the escape sequence \Q \E to quote metacharacters so that they won't be interpreted:
print if m/\Q$user_pattern\E/o;
You can make a script run with the privileges of its owner by setting its "s" bit:
chmod u+s foo.plYou can make it run with the privileges of its owner's group by setting the s bit in the group field:
chmod g+s foo.plHowever, many Unix systems contain a hole that allows suid scripts to be subverted. This hole affects only scripts, not compiled programs. On such systems, an attempt to execute a Perl script with the suid bits set will result in a nasty error message from Perl itself.
You have two options on such systems:
ftp://rtfm.mit.edu/pub/usenet-by-group/comp.lang.perl/
#include <unistd.h> void main () { execl("/usr/local/bin/perl","foo.pl","/local/web/cgi-bin/foo.pl",NULL); }After compiling this program, make it suid. It will run under its owner's permission, launching a Perl interpreter and executing the statements in the file "foo.pl".
Another option is to run the server itself as a user that has sufficient privileges to do whatever the scripts need to do. If you're using the CERN server, you can even run as a different user for each script. See the CERN documentation for details.
Up to Table of Contents | ||
Back to CGI Scripts | Forward to Server Logs and Privacy |
Lincoln D. Stein (lstein@cshl.org)
Last modified: Mon Sep 13 13:52:45 EDT 1999