Knowledge Base

Authoring/Development - Writing Secure Perl Scripts

Custom Perl CGI programs can be an effective way to tailor the experience of your site to the needs of your viewers, and to encourage return visits. A good CGI programmer must keep in mind, however, that a small percentage of these viewers will have harmful intentions. Security should always be your foremost concern.

This tutorial is intended as a beginner's overview to securing your CGI scripts. It is certainly not all-encompassing, nor is it intended to be. There are many sophisticated ways to attack a script not detailed here -- we encourage you to read all you can on the subject and incorporate as many safety features as you can into your code.

The #1 Rule

The number one, overriding concept to remember while writing your CGI scripts is: You cannot trust user input.

Assuming that data received from a form is safe can easily result in your script being compromised.

The classic example:

Say you have a form-based menu. The HTML code might look like:

<select name="menu">
<option value="pageone.html">Page One
<option value="pagetwo.html">Page Two
<input type="submit">

If "Page One" is selected, your script opens the file pageone.html and outputs it to the browser. Seem safe?

Say the user saves the source code of your page , then alters the "Page One" line to read:

<option value="/var/mail/yourusername">Page One

and runs his altered version of the script via cgiwrap.

This person would now have the entire contents of your mailbox.

Processing Form Input For Safety

To guard against this sort of attack, you must never allow a full path to be used in a form input. You should also prevent the use of "..", since it can be used to back up into directories that shouldn't be accessed. To remove these characters from an form input that represents a filename, you might use a program line like:

$forminput =~ s/(\/|\.\.)//g;

This removes all forward slashes and .. combinations from the variable. Ideally though, in the previous example, you would use an associative array to define keywords to represent file names, like:

%files = (
"p1", "pageone.html",
"p2", "pagetwo.html",
"p3", "pagethree.html"

Then you would use the keys p1, p2, etcetera in your form instead of the actual filename. If $files{$key} (where $key is the value from the form) exists, then use that filename. If it doesn't, then the form has been altered and you should output an error page (and possibly log the infraction to your liking). This technique is usable in many cases, and is far preferable to gleaning actual filenames from form fields.

Protecting Your E-Mail Forms from Exploits

Be warned that we come in contact with many successful exploits against customer Perl scripts that send e-mails. These exploits are intended to send out junk e-mail across the Internet.

In many cases, we are able to detect such abuses and prevent the e-mail from being sent out. In other cases, though, it is indistinguishable from normal activity.

When we locate a vulnerable script that is actively being attacked, we are disabling it and notifying the customer.

How The Exploit Typically Works

In almost all cases, the attack works by placing a newline character (represented by \n in the following example) in the field that asks for the user's e-mail address. For instance, they might put:\nCC:,
If a script uses that e-mail address as the From header of the resultant e-mail, and does not carefully check the contents before using it, the script is likely to create these headers:
And the e-mail is sent to all of the addresses.

This exploit can be done against any form data that is placed in the headers of an e-mail, be it From, Subject, or another field.

How To Protect Your Site

Our strong recommendation is to use the and cgiemail scripts we provide in our system cgi-bin wherever possible. cgiemail in particular is very flexible and can suit most e-mail gateway needs.

Those scripts have been extensively tested and hardened against this and other types of exploits.

If you need to use a custom Perl script as an e-mail gateway, please ensure you are filtering all data received from the form, and remove any unexpected characters. For instance, this sample code snippet would check for all the non-printable codes in the standard ASCII set, including null bytes and newlines, and exit immediately if any are found.

if ($EMAIL =~ /[\000-\037]/) { die }
In this example, $EMAIL is used to represent a variable that contains form data. Such filtering should be done on all variables containing form data that are used in the headers of the e-mail.

Limiting by HTTP_REFERER

Another way to guard against this and other types of attacks is to limit execution of the scripts to your domain name only. This can be done by checking the HTTP_REFERER variable (referrer is misspelled for historical reasons). To limit execution to the domain name, your code might look something like:

if (($ENV{'HTTP_REFERER'}) &&
($ENV{'HTTP_REFERER'} !~ /^http:\/\/ {
# output error page or take other actions

The HTTP_REFERER variable is not always present, so you should make sure it exists and if so make sure it's from your domain name. Note that this is not 100% reliable, as a sophisticated user can fake the HTTP_REFERER variable (or any environment variable, for that matter) -- but it's a decent protective step to take in conjunction with other methods.

Guarding System Calls

Another area in which input to forms must be carefully evaluated is if you are making system calls through the use of commands such as system or exec, or through the use of "back ticks".

For instance, you might be using a system call to grep to search a text file for user keywords, like:

system("grep $userinput datafile.txt > file.txt");

That's fine if the user input is "apples", but if the user inputs "; cd / ; rm -rf * ; echo", then you've just willingly run

grep ; cd / ; rm -rf * ; echo datafile.txt > file.txt

and all files the webserver can write to will be deleted.

Your first protection is to not use system calls, ever. This is our strong recommendation. If you must use them, however, you should do two things:

  1. First, remove any of these characters -- < > | ; -- from the input using the same technique used earlier to remove / and ..
  2. Second, you should send the arguments to the system call as a list, like:

    system("grep",$userinput,"datafile.txt","> file.txt");

    This forces the data in $userinput to be evaluated solely as an argument to grep, no matter what it contains.

Generating SSI Pages via CGI

The last concept discussed is protecting yourself when writing user data to pages that use server side includes (SSI). If you use SSI on a guestbook page, for instance, you must guard against a user including SSI codes within their entry. For example, they might include something like:

<!--#exec cmd="/bin/ls /usr/home/yourusername" -->

in their entry. A regular expression such as:

$forminput =~ s/<!*[^>]*>//gm;

should remove such entries before you write it out to the file.


These are just a few of the basics of CGI Security. We highly recommend that you use all of these techniques as they apply. We recommend even more highly that you read all that you can on the subject, as there are certainly many more ways to attack a script, and the more you protect yourself the better off you'll be.

Recommended Reading

» The WWW Security FAQ

Shortcut To This Article:

Related Information