Before we continue with the book, let’s take a few minutes for some cautionary words. Programs written for system administration have a twist that makes them different from most other programs. On Unix and NT/2000 they are often run with elevated privileges, i.e., as root or Administrator. With this power comes responsibility. There is an extra onus on us as programmers to write secure code. We write code that can and will bypass the security restrictions placed on mere mortals. If we are not careful, less “ethical” users may use flaws in our code for nefarious purposes. Here are some of the issues you should consider when you use Perl under these circumstances.
By all means, use Perl. But if you can, avoid having your code run in a privileged context. Most tasks do not require root or Administrator privileges. For example, your log analysis program probably does not need to run as root. Create another, less privileged user for this sort of automation. Have a small, dedicated, privileged program hand the data to that user if necessary, and then use that user to perform the analysis.
Sometimes you can’t avoid running a script as root or Administrator. For instance, a mail delivery program you create may need to be able to write to a file as any user on the system. Programs like these should shed their omnipotence as soon as possible during their run.
Perl programs running under Unix and
Linux can set the $<
and
$>
variables:
# permanently drops privs ($<,$>) = (getpwnam('nobody'),getpwnam('nobody'));
This sets the real and effective user IDs to that of
nobody, hopefully an underprivileged user. To be
even more thorough, you may wish to use $(
and
$)
to change the real and effective group IDs as
well.
Windows NT and Windows 2000 do
not have user IDs per se, but there are similar processes for
dropping privileges. Windows 2000 has a feature called
“RunAs” which can be used to run processes as a different
user. Under both Windows NT and Windows 2000, users with the user
right of Act as part of the operating system
can impersonate other users. This
user right can be set using the User Manager or
User Manager for Domains
program:
Under the “Policies” menu, choose “User Rights.”
Select the “Show Advanced User Rights” check box.
Choose “Act as part of the operating system” from the drop-down selector.
Select “Add...” and choose the users or groups who should receive this right. You may need to choose “Show Users” if you will be adding this right to a specific user.
If this is an interactive user, that user must log out and back in again to make use of this new user right.
You will also need to add the rights Replace a process level token
and in some cases
Bypass traverse checking
(see the
Win32::AdminMisc
documentation). Once you have
assigned these rights to a user, that user can run Perl scripts with
LogonAsUser( )
from David Roth’s
Win32::AdminMisc
module found at http://www.roth.net:
use Win32::AdminMisc; die "Unable to impersonate $user\n" if (!Win32::AdminMisc::LogonAsUser('',$user,$userpw);
Note: there is some danger here, because unlike the previous example,
you must pass the password of the user to the LogonAsUser( )
call.
When reading important data like configuration files, test for unsafe conditions first. For instance, you may wish to check that the file and all of the directories that hold the file are not writeable (since that means someone could have tampered with them). There’s a good recipe for testing this in Chapter 8 of the Perl Cookbook, by Tom Christiansen and Nathan Torkington (O’Reilly).
The
other concern is user input. Never trust that input from a user is
palatable. Even if you explicitly print Please answer Y or N:
, there is nothing preventing the user from
answering with 2049 random characters (either out of spite, malice,
or because they stepped away from the keyboard and a two-year-old
came over to the keyboard instead).
User input can be the cause of
even more subtle trouble. My favorite example is the “Poison
NULL Byte” exploit as reported in an article on Perl CGI
problems. Be sure to see the whole article (cited in the References
section at the end of this chapter). This particular exploit takes
advantage of the difference between Perl’s handling of a NULL
(\000
) byte in a string and the handling done by
the C libraries on a system. To Perl, there is nothing special about
this character. To the libraries, this character is used to indicate
the end of a string.
In practical terms, this means that it is possible for a user to evade simple security tests. One example given in the article is that of a password-changing program whose code looks like this:
if ($user ne "root"){ <call the necessary C library routine>
}
If $user
is set to root\000
(i.e., root
followed by a NULL byte) then the
above test will succeed. When that string is passed to the underlying
library, the string will be treated as just
root, and someone will have just walked right
past the security check. If not caught, this same exploit will allow
access to random files and other resources. The easiest way to avoid
being caught by this exploit is to sanitize your input with something
like:
$input =~ tr /\000//d;
This is just one example of how user input can get our programs in trouble. Because user input can be so problematic, Perl has a security precaution called “taint mode.” See the perlsec manpage that ships with Perl for an excellent discussion of “taintedness” and other security precautions.
If your program can write or append to every single file on the local filesystem, you need to take special care with the how, where, and when it writes data. On Unix systems, this is especially important because symbolic links make file switching and redirection easy. Unless your program is diligent, it may find itself writing to the wrong file or device. There are two classes of programs where this concern comes especially into play.
Programs that append data to a file fall into the first class. The steps your program should take in sequence before appending to a file are:
Check the file’s attributes before opening it using
stat( )
and the normal file test operators. Make sure it is not a hard or soft link, that it has the appropriate permissions and ownership, etc.Open the file for appending.
stat( )
the open filehandle.Compare the values from steps 1 and 3 to be sure that you have an open file handle to the file you intended.
You can see the bigbuffy program in Chapter 9, for sample code that uses this procedure.
Programs that use temporary files or directories are in the second class. You’ve often seen code like this:
open(TEMPFILE,">/tmp/temp.$$") or die "unable to write /tmp/temp.$$:$!\n";
Unfortunately, that’s not sufficiently secure on a multiuser
machine. The process ID ($$
) sequence on most
machines is easily predictable, which means the next temporary
filename your script will use is equally predictable. If someone can
predict that name, they may be able to get there first and
that’s usually bad news.
Some operating systems have library calls that will produce a
temporary filename using a decent randomization algorithm. To test
your operating system, you can run the following code. If the printed
names look reasonably random to you, POSIX::tmpnam( )
is a safe bet. If not, you may have to roll your own
random filename generation function:
use POSIX qw(tmpnam); for (1..20){ print POSIX::tmpnam( ),"\n"; }
Once you have a filename that cannot be guessed, you will need to open it securely:
sysopen(TEMPFILE,$tmpname,O_RDWR|O_CREAT|O_EXCL,0666);
There is a second, easier way to perform these two steps (getting and
opening a temporary file). The IO::File->new_tmpfile( )
method from the IO::File
module will
not only pick a good name (if the system libraries support this), but
it will also open the file for you for reading and writing.
Examples of POSIX::tmpnam( )
and
IO::File->new_tmpfile( )
along with other
information about this topic can be found in Chapter 7 of the
Perl Cookbook. Tim Jenness’
File::Temp
module also attempts to provide secure
temporary file operations.
Whenever possible, avoid writing code that is susceptible to race condition exploits. The traditional race condition starts with the assumption that the following sequence is valid:
Your program will amass some data.
Your program can then act on that data.
If users can break into this sequence, let’s say at step 1.5, and make some key substitutions, they may cause trouble. If they can get your program in step 2 to naively act upon different data than it found in step 1, they have effectively exploited a race condition (i.e., their program won the race to get at the data in question). Other race conditions occur if you do not handle file locking properly.
Race conditions often show up in system administration programs that scan the filesystem as a first pass and then change things in a second pass. Nefarious users may be able to make changes to the filesystem right after the scanner pass so that changes are made to the wrong file. Make sure your code does not leave gaps like this open.
It is important to remember that system administration is fun. Not all the time, and not when you have to deal with the most frustrating of problems, but there’s a definite enjoyment to be found. There is a real pleasure in supporting other people and building the infrastructures that make other people’s lives better. When the collection of Perl programs you’ve just written brings other people together for a common purpose, there is joy.
Now that you are ready, let’s get to work on those wires.
Get Perl for System Administration now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.