19.4.3. Discussion
Many of these suggestions are good ideas for any program—using
warnings and checking the return values of your system calls are
obviously applicable even when security isn't the first thing on your
mind. The use warnings pragma makes Perl issue
warnings about dubious constructs, like using an undefined variable
as though it had a legitimate value, or writing to a read-only
filehandle.
Apart from unanticipated shell escapes, the most common security
threat lies in forged values in a form submission. It's trivial for
anyone to save the source to your form, edit the HTML, and submit the
altered form. Even if you're certain that a field can return only
"yes" or "no", they can always
edit it up to return "maybe" instead. Even fields
marked as type HIDDEN in the form can be tampered
with. If the program at the other end blindly trusts its form values,
it can be fooled into deleting files, creating new user accounts,
mailing password or credit card databases, or innumerable other
malicious abuses. This is why you must never blindly trust data (like
prices) stored in hidden fields when writing CGI shopping cart
applications.
Even worse is when the CGI script uses a form value as the basis of a
filename to open or a command to run. Bogus values submitted to the
script could trick it into opening arbitrary files. Situations like
this are precisely why Perl has a taint mode. If a program runs
setuid or in taint mode, data from program arguments, environment
variables, directory listings, or files are considered tainted, and
cannot be used directly or indirectly to affect the outside world.
For instance, when running in taint mode:
#!/usr/bin/perl -T
open(FH, "> $ARGV[0]") or die;
Perl warns with:
Insecure dependency in open while running with -T switch at ...
This is because $ARGV[0] (having come from outside
your program) is not trustworthy. The only way to change tainted data
into untainted data is by using regular expression backreferences:
$file = $ARGV[0]; # $file tainted
unless ($file =~ m#^([\w.-]+)$#) { # $1 is untainted
die "filename '$file' has invalid characters.\n";
}
$file = $1; # $file untainted
Tainted data can come from anything outside your program, such as
from your program arguments or environment variables, the results of
reading from filehandles or directory handles, and
stat or locale information. Operations considered
insecure with tainted data include
system(STRING),
exec(STRING),
backticks, glob, open with any
access mode except read-only, unlink,
mkdir, rmdir,
chown, chmod,
umask, link,
symlin k, the -s
command-line switch, kill,
require, eval,
truncate, ioctl,
fcntl, socket,
socketpair, bind,
connect, chdir,
chroot, setpgrp,
setpriority, and syscall.
Race conditions crop up even in apparently innocuous places. Consider
what would happen if not one but many copies of the following code
ran simultaneously.
unless (-e $filename) { # WRONG!
open(FH, "> $filename");
# ...
}
There's a race between testing whether the file exists and opening it
for writing. Similar race conditions can occur in such common
situations as reading data from a file, updating the data, and
writing it back out to that file.
A setuid CGI script runs with different permissions than the web
server does. This lets the CGI script access resources (files, shadow
password databases, etc) that it otherwise could not. This can be
convenient, but it can also be dangerous. Weaknesses in setuid
scripts may let crackers access not only files that the low-privilege
web server user can access, but also any that could be accessed by
the user the script runs as. For a poorly written setuid root script,
this could let anyone change passwords, delete files, read credit
card records, and other malicious acts. Always make sure your
programs run with the lowest privilege possible, normally the user
the web server runs as: nobody.
Finally (and this recommendation may be the hardest to follow), be
conscious of the physical path your network traffic takes. Are you
sending passwords over an unencrypted connection? Do these
unencrypted passwords travel through insecure networks? A form's
PASSWORD input field only protects you from someone looking over your
shoulder. Always use SSL when real passwords are involved. If you're
serious about security, fire up your browser and a packet sniffer to
see how easily your traffic is decoded.