Because CGI programs allow external users to run programs on systems they would not otherwise have access on, all CGI programs represent a potential security risk. You want to minimize your exposure.
Sanity-check everything, including all form widget return values, even hidden widgets or values generated by JavaScript code. Many people naïvely assume that just because they tell JavaScript to check the form's values before the form is submitted, the form's values will actually be checked. Not at all! The user can trivially circumvent this by disabling JavaScript in their browser, by downloading the form and altering the JavaScript, or quit by talking HTTP without a browser using any of the examples in Chapter 20, Web Automation.
Run with -w and use
strict
to make sure Perl isn't assuming things incorrectly.
Don't run anything setuid unless you absolutely must. If you must, think about running setgid instead if you can. Certainly avoid setuid root at all costs. If you must run setuid or setgid, use a wrapper unless Perl is convinced your system has secure setuid scripts and you know what this means.
Always encode login passwords, credit card numbers, social security numbers, and anything else you'd not care to read pasted across the front page of your local newspaper. Use a secure protocol like SSL when dealing with such data.
Many of these suggestions are good ideas for any program - using -w and checking the return values of your system calls are obviously applicable even when security isn't the first thing on your mind. The -w switch 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. 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 else has the -T switch active, any data coming in through program arguments, environment variables, directory listings, or a file, are considered tainted, and cannot be used directly or indirectly to affect the outside world.
Running under taint mode, Perl insists that you set your path variable first, even if specifying a complete pathname when you call a program. That's because you have no assurance that the command you run won't turn around and invoke some other program using a relative pathname. You must also untaint any externally derived data for safety.
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 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
.
A common attack exploits what's known as a race condition. That's a situation where, between two actions of yours, an attacker can race in and change something to make your program misbehave. A notorious race condition occurred in the way older Unix kernels ran setuid scripts: between the kernel reading the file to find which interpreter to run, and the now-setuid interpreter reading the file, a malicious person could substitute their own script.
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. Still worse, if someone replaced the file with a link to something important, like one of your personal configuration files, the above code would erase that file. The correct way to do this is to do a non-destructive create with the sysopen
function, described in Recipe 7.1.
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. This is why you should always make sure your programs run with the lowest possible privilege, 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.
The section on "Cooperating with Strangers" in Chapter 6 of Programming Perl; perlsec (1); the CGI and HTTP specs and the CGI Security FAQ, all mentioned in the Introduction to this chapter; the section on "Avoiding Denial of Service Attacks" in the standard CGI module documentation; Recipe 19.6