Skip to main content.

Web Based Programming Tutorials

Homepage | Forum - Join the forum to discuss anything related to programming! | Programming Resources

Web Programming with Perl5

untitled



3

Security on the Web






Any type of server you run at your site may introduce the potential for inappropriate access to your site, and a Web server is no exception. This chapter will attempt to acquaint you with some of the most common security issues that confront the Webmaster or Web team and how to design policies and implement procedures to deal with them.

The first thing to consider when choosing server software to run at your site is its origin. Some Web servers are freely available in source form. Having the source code that comprises the server available for inspection may lend to your assurance that it is trustworthy or reliable. At the very least, you're able to inspect the source code and possibly make modifications to it, to make it conform to your own policies. On the other hand, access to the source code also gives the unsavory few the means to study the code for the purpose of cracking your system. Other servers are proprietary, and their source code is not freely available. Precompiled servers that run on platforms other than UNIX are the most common, but there are also precompiled proprietary servers available for various UNIX platforms. Since neither the admin nor anyone else can study their code, they introduce the notion known as "Security by Obscurity," both for their inner workings and for their delivery and encryption mechanisms. The origin of security by obscurity is to be found within the world of cryptography, and it actually refers to certain cryptographic algorithms that rely on the secrecy of certain components to be reliable and safe. Cryptographic techniques used in Web service will be discussed later in this chapter. Within the realm of programs you might decide to run on your computer, however, the notion of security by obscurity is not necessarily a good thing for a number of reasons.

Whichever type of server you choose to run and architecture you choose to run it under, you'll need to be conscientious and methodical in all of your activities relating to the management of the server and its archive. Just because a server is purported to be safe and reliable doesn't mean that a trained monkey can manage it. The Webmaster should also make an effort to stay up-to-date and well informed regarding the latest information on security problems related to the server, the archive, and the OS the server runs under. The day-to-day activities and procedures that comprise a sound security policy are highly dynamic in nature, due to the constant struggle between those who would crack your system and those who discover these holes and dispense the information necessary to close them up.



NOTE:

One important thing you can do to prevent unwanted access and/or retrievals of your archive's contents is to plan your archive's layout well. Chapter 14, "Archive and Document Management," discusses this further. Good archive planning and an established policy for the various aspects of server management and archive updating lend to your ability to configure the server's security mechanisms on a discrete but not overly restrictive basis. After all, too much rigor may tend to chase away well-intentioned but perhaps slightly inexperienced browsers and/or customers.


General Server Security

The security mechanisms the server itself provides vary from server to server and from architecture to architecture. Whatever server you choose to run, you should understand security and spend the time to implement them. Especially if you're running under a multi-user architecture like UNIX, you can easily overlook an item that may cause a potential hole.



NOTE:

It's the general consensus that of all the platforms on which you can run a WWW server, the one that is least likely to cause you security problems is the Macintosh. The fact that the Macintosh doesn't provide the capability to log in from anywhere but the console reduces the risks involved with providing external access to its filesystem. This also makes it a bit more difficult to manage the archive's contents as a team, of course, but this issue can be worked around through the Macintosh's file-sharing mechanisms. You may wish to investigate this alternative to a UNIX server.


Server Configuration Options

Servers running under multi-user machines will ordinarily "run as" some particular userid. The administrator will often create a faux userid to implement this.



CAUTION:

The server should never run under any administrative userid that has special permissions on the server or, for that matter, any other user's ID. Period.


Under UNIX, the httpd will ordinarily be started by the superuser and will then change to the userid specified in the server's httpd.conf for each new connection. The directive within the access.conf file to configure the server's user permissions may look like the following if the administrator has decided to run the httpd under the commonly used nobody userid:

# User/Group: The name (or #number) of the user/group to run httpd as.

User nobody

Group nobody

It is a simple task to make this entry into the httpd.conf, but what if it ever changes? You might want to inspect the value for this parameter once in a while to make sure your httpd is running under the UID you want it to. This will serve as a nice way to introduce the features of the HTTPD module and some of its capabilities. The HTTPD::config module is available at any CPAN archive.

use HTTPD::Config;

$conf =  `/usr/local/etc/httpd/conf';

@files = qw(httpd.conf);



$config = new HTTPD::Config (SERVER => NCSA,

                             SERVER_ROOT => $conf,

                             FILES => [@files]);

print "The current userid and group for httpd: ",

        $config->user, " , ",  $config->group,"\n";

As you can see, the HTTPD module provides a method for each configurable parameter within the httpd.conf file. Assuming that the user and group for your httpd are nobody and nogroup, respectively, the preceding script prints the following:

The current userid and group for httpd: nobody , nogroup

For this example, we've parsed only the httpd.conf file, but the HTTPD module allows you to parse any or all of the configuration files for the httpd.

This example is easily extended to print a nice report for any or all of the current values for the configurable parameters in the httpd configuration files. Some of the more important ones can be displayed with the following script:

use HTTPD::Config;

$conf =  `/usr/local/etc/httpd/conf';

@files = qw(httpd.conf srm.conf access.conf);

$V= new HTTPD::Config (SERVER => NCSA,

                             SERVER_ROOT => $conf,

                             FILES => [@files]);

print "Userid: ", $V->user,"\n";

print "Group: ", $V->group,"\n";

print "Administrator is: ", $V->server_admin,"\n";

print "Running at port: ", $V->port,"\n";

print "Access filename: ",$V->access_file_name,"\n";

This gives:

Userid: nobody

Group: nogroup

Administrator is: wmiddlet@adobe.com

Running at port: 80

Access filename: .privaccess

We'll be developing this example further using the HTTPD module to keep an eye on the directory-specific parameters a bit later. First, though, we need to take a look at a few more relevant issues. (For this example and others to follow, I used an unreleased version of HTTPD:: config. By the time you read this, the released version will be available in Doug MacEachern's CPAN directory.)

Other security issues to consider when configuring a server under UNIX include the following options:

So, given all of the configuration options available to protect your server within the httpd configuration files, we can use Perl to help ensure we haven't overlooked anything and that the important variables we've mentioned are set according to our preference. The HTTPD module gives us this capability. The following script builds on our previous simple examples and gives additional information on the directory-specific configuration options discussed in the preceding list:

use HTTPD::Config;

$conf =  `/usr/local/etc/httpd/conf';

@files = qw(httpd.conf srm.conf access.conf);

$V= new HTTPD::Config (SERVER => NCSA,

                             SERVER_ROOT => $conf,

                             FILES => [@files]);

print "Userid: ", $V->user,"\n";

print "Group: ", $V->group,"\n";

print "Administrator is: ", $V->server_admin,"\n";

print "Running at port: ", $V->port,"\n";

print "Access filename: ",$V->access_file_name,"\n";

print "User Directory: ", $V->user_dir,"\n";

print "Global Types:\t", join("\n\t\t",$V->add_type);

print "\n\n";

foreach $dir (keys %{$V->{`Directory'}}){

    print "Options for Directory: $dir\n";

    while(($opt,$val) = each %{$V->{`Directory'}{$dir}{`OPTIONS'}}){

        print "\t",$opt, " : ", @{$val},"\n";

    }

    print "\n";

}

Which gives the following output:

Userid: nobody

Group: nogroup

Administrator is: wmiddlet@adobe.com

Running at port: 80

Access filename: .privaccess

User Directory: public_html

Global Types:   text/x-server-parsed-html .shtml

                application/x-httpd-cgi .cgi

Options for Directory: /usr/local/etc/httpd/cgi-bin

        Options : None

        AllowOverride : None

Options for Directory: /usr/local/etc/httpd/htdocs

        order : allow,deny

        Options : Indexes FollowSymLinks

        allow : from all

        AllowOverride : All

Now this example has developed into a fairly useful script, which could be run, perhaps nightly, and compared to some known preferences. If anything has changed, either accidentally or otherwise, in your site's configuration, you can take action immediately.

You'll notice from the preceding output that two additional types have been enabled to allow CGI scripts anywhere via the .cgi extension and server-side includes or server-side parsing via the .shtml extension. As mentioned previously, these options should be considered very carefully before enabling them in the first place.

If you're using the Apache server, there are some additional configuration directives you might be interested in; one in particular is called AddHandler. As of the writing of this chapter, the HTTPD module doesn't automatically create a method to dump these out because they're a fairly recent feature for Apache httpd servers. Their intent is to configure the Apache server to use a dynamic module (akin to a Perl5 extension) to handle certain types of requests, as opposed to a CGI script or some other mechanism. In order to get at their settings and since we don't have a built-in method yet, we'll cheat a bit and access the HTTPD object's instance variables directly to extend the previous script to handle the additional parameter:

use HTTPD::Config;

$conf =  `/usr/local/etc/httpd/conf';

@files = qw(httpd.conf srm.conf access.conf);

$V= new HTTPD::Config (SERVER => Apache,

                             SERVER_ROOT => $conf,

                             FILES => [@files]);

print "Userid: ", $V->user,"\n";

print "Group: ", $V->group,"\n";

print "Administrator is: ", $V->server_admin,"\n";

print "Running at port: ", $V->port,"\n";

print "Access filename: ",$V->access_file_name,"\n";

print "User Directory: ", $V->user_dir,"\n";

print "Global Types:\t", join("\n\t\t",$V->add_type),"\n";

print "Global Handlers:\t", join("\n\t\t\t",@{$V->{AddHandler}});

print "\n\n";

foreach $dir (keys %{$V->{`Directory'}}){

    print "Options for Directory: $dir\n";

    while(($opt,$val) = each %{$V->{`Directory'}{$dir}{`OPTIONS'}}){

        print "\t",$opt, " : ", @{$val},"\n";

    }

    print "\n";

}

Now we get the complete output for our Apache server:

Userid: nobody

Group: nogroup

Administrator is: wmiddlet@adobe.com

Running at port: 80

Access filename: .privaccess

User Directory: public_html

Global Types:  text/x-server-parsed-html .shtml

                application/x-httpd-cgi .cgi

Global Handlers:        cgi-script .cgi

                        server-parsed .shtml

                        byte-serve  .pdf

Options for Directory: /usr/local/etc/httpd/cgi-bin

        Options : None

        AllowOverride : None

Options for Directory: /usr/local/etc/httpd/htdocs

        AddType : text/html .shtml

        order : allow,deny

        Options : Indexes FollowSymLinks

        allow : from all

        AllowOverride : All

This output indicates that there are three handler modules configured for our Apache server, and they each have specific extensions that trigger them. Note how we created the HTTPD::Config object using the value Apache for the SERVER parameter. As mentioned, we also accessed the HTTPD object directly via the $V->{`AddType'} reference, which pointed at an array. Ideally, we would submit a patch to the author of the HTTPD module so that the preferred mechanism would work.

File Permissions Setup and Monitoring Changes in the System

Another aspect of server configuration to consider is the permissions on the programs, directories, logs, and configuration files themselves, which comprise the installed httpd server. In general, under UNIX, the installation of an http server will create the following directories with their intent:

cgi-bin CGI programs to be executed by the server
conf Configuration files for the server
htdocs The top-level directory for the archive
icons/images Common image files and icon bitmaps
logs Logfiles are created here by httpd
support Programs used for maintenance of the server

The important thing here is to be sure that the appropriate access is afforded to the appropriate components of this structure, while maintaining privacy and some level of security through obscurity against uninvited snoopers. If you're managing your archive as a team, you will probably create a group-ID for the members of the team who will be modifying the contents. These people may also need to access the files within the directories listed in the preceding list. Thus, it's recommended (see Practical Internet and UNIX Security, O'Reilly, 1996) to make all of the files and directories within the list owned by root and accessible by the group you've created for the Web team--something like this:

drwx--x--x  2 root www         512 May 10 21:36 cgi-bin/

drwx------  2 root www         512 Jul 20 16:11 conf/

drwxr-xr-x  3 root www         512 May 14 16:02 htdocs/

-rwx------  1 root www      155648 May  5 15:33 httpd*

drwxr-xr-x  2 root www        1024 Feb 17 00:32 icons/

drwx------  2 root www         512 Jun 23 11:28 logs/

drwxr-xr-x  2 root www         512 Feb 17 00:32 support/

In this case, the restrictions on the logs directory or the conf directory may be excessive, again depending on the level of access you wish to provide. If the person who has the root password can't be bothered to create new directories under the htdocs directory, to truncate/archive the logfiles periodically, or to modify the configuration options within the config files, then these files or directories may need to have write-perms for the group www, or possibly ownership by the userid, www. The most important thing is that root owns the server binary itself, httpd, and that the userid the server runs under (nobody) has no write permissions to anything within this hierarchy.

It's a good idea to use some sort of security package, like TripWire or COPS, to occasionally verify that these permissions haven't been changed. Such a practice is also useful for the detection of unwanted files or changed files within your archive. This will be discussed further in the next section. We'll take a closer look at these tools at the end of the chapter. For now, we can add a few lines to the previous script to check some of the most important of these permissions for us:

use HTTPD::Config;

require "stat.pl";

$conf =  `/usr/local/etc/httpd/conf';

@files = qw(httpd.conf srm.conf access.conf);

$V= new HTTPD::Config (SERVER => Apache,

                             SERVER_ROOT => $conf,

                             FILES => [@files]);

print "Userid: ", $V->user,"\n";

print "Group: ", $V->group,"\n";

print "Administrator is: ", $V->server_admin,"\n";

print "Running at port: ", $V->port,"\n";

print "Access filename: ",$V->access_file_name,"\n";

print "User Directory: ", $V->user_dir,"\n";

print "Global Types:\t", join("\n\t\t",$V->add_type),"\n";

print "\n\n";

foreach $dir (keys %{$V->{`Directory'}}){

    print "Options for Directory: $dir\n";

    while(($opt,$val) = each %{$V->{`Directory'}{$dir}{`OPTIONS'}}){

        print "\t",$opt, " : ", @{$val},"\n";

    }

}

# rudimentary permissions checking

$webuser = (getpwnam($V->user))[2];

opendir(ROOT,$V->server_root);

@files = grep(!/\.\.?/,readdir(ROOT));

closedir(ROOT);

foreach $f (@files){

    @s = Stat($V->server_root."/$f");

    if($s[$ST_UID] == $webuser){

        print "Warning: ",$V->server_root,"/$f is owned by ",

                $V->user,"\n\n";

    }

    if($f eq "httpd"){

        if(($s[$ST_MODE] != 0100700) or ($s[$ST_UID] != 0)){

            print "\tWarning: ",$V->server_root,"/httpd may have\n";

            print "\tpermission problems.  Recommend root ownership\n";

            print "\tand readable, writable and executable only by\n";

            print "\troot user\n";

        }

    }

}

Now, when we run our simple security script, we'll get a warning if anything in the RootDir is owned by the userid that runs the server, or if the httpd itself isn't owned by root with the permissions specified in the preceding code. Note that we used the old stat.pl library in this example, as well. It provides a simplified interface to the stat(2) function.

Data Security

A Web archive may have a specific intent for any or all of its elements. The contents of your archive may be entirely intended for public access, without any constraints at all, or you may wish to have specific permissions set up for each and every element. In this section, we'll take a look at how you can do either.

General ConsiderationsGlobal versus Local Configuration of Permissions

Obviously, if you don't care who accesses your archive's contents, you won't need to worry too much about data security, with the exception of general permissions being correctly set. If your archive provides documents that are intended for a specific audience, such as registered users or paying customers, then you'll need to use the additional features of the httpd server to limit access to those documents to the audience you intend them for. Additionally, you may use CGI scripts, Netscape cookies, hidden variables, or other state-retention mechanisms to assure proper access. These topics will be discussed in Chapter 16, "Advanced CGI/HTML."

Most http servers provide you with the capability to give access to specific files and directories on an individual basis, using one of two methods. The files and tools you'll be configuring to enable this capability differ in format, depending on which server you're running, but generally are

conf/access.conf Global access configuration file
htaccess Directory-specific access file



when using the Apache or NCSA servers. In the examples that follow, we'll assume you're using one or the other of these servers. The other Web server, from CERN, won't be discussed or used in the examples here for the sake of brevity.

For simplicity and portability, it's probably easier to use the per-directory .htaccess files, but this technique is generally not favored as strongly as using the global access-control file, presumably because it's easier to control all rights and permissions from one place. If you do decide to use the per-directory method, then be aware that the .htaccess file can be specified in the conf/srm.conf file to be whatever name you specify, and it is recommended that you use a name other than .htaccess in order to avoid arbitrary retrievals of your access configuration files themselves. (There is a bug in some servers that allows the client to fetch your access-configuration files directly.)

Directory Access Configuration

If you decide to allow per-directory configuration files, you'll need to enable them in the global access.conf file with a good deal of consideration and care. Assuming you have a <Directory> entry for the DocumentRoot, you can enable certain capabilities for individual directories beneath the DocumentRoot on an as-needed basis.

Let's suppose, for the purpose of demonstration, that your archive consists of the DocumentRoot:

/usr/local/etc/httpd/htdocs

And beneath DocumentRoot, you have a subdirectory:

/usr/local/etc/httpd/htdocs/test1

Now assume that there exists, for DocumentRoot, an entry in the access.conf file that looks something like this:

<Directory /usr/local/etc/httpd/htdocs>



AuthType Basic

AuthName PasswordAdmin

AuthUserFile /usr/local/etc/httpd/conf/.htpasswd



AccessFileName .myhtaccess



AddType text/html .shtml



Options Indexes SymLinksIfOwnerMatch IncludesNOEXEC



AllowOverride AuthConfig FileInfo Indexes Limit



</Directory>

This entry allows any directory beneath DocumentRoot to have its own .privaccess files, which can then override any of the directives specified for DocumentRoot except the Options directive, which we don't want to let anyone specify beyond what is already allowed. Note that the Options directive is already used to turn on the automatic directory indexes (when index.html doesn't exist) and to follow symlinks if the owner of the symlink and what it points to match, as well as allowing server-side includes, but not the #exec and #include directives within server-side includes files.

Now, assume that the directory test1 has some proprietary or confidential information within it. You could assure that nothing is left to chance by configuring its directory entry in the global access.conf to disallow any overrides or options, like this:

<Directory /usr/local/etc/httpd/htdocs/test1>



AllowOverride None



Options None



<Limit GET POST>

require valid-user

</Limit>



</Directory>

Note that we also specify here that any access to this directory also be validated from the valid users in the AuthUserFile, inherited from the directives for DocumentRoot. We'll look more at user/group authentication in a bit.

You can use Perl and the HTTPD modules to verify that your access configurations for any given file in the archive are what you expect them to be. Here we'll modify our previous script to give us some additional information on the configuration directives for each directory specified in access.conf:

use HTTPD::Config;

require "stat.pl";

$conf =  `/usr/local/etc/httpd/conf';

@files = qw(httpd.conf srm.conf access.conf);

$V= new HTTPD::Config (SERVER => Apache,

                             SERVER_ROOT => $conf,

                             FILES => [@files]);



print "Userid: ", $V->user,"\n";

print "Group: ", $V->group,"\n";

print "Administrator is: ", $V->server_admin,"\n";

print "Running at port: ", $V->port,"\n";

print "Access filename: ",$V->access_file_name,"\n";

print "User Directory: ", $V->user_dir,"\n";

print "Global Types:\t", join("\n\t\t",$V->add_type),"\n";

print "\n\n";



foreach $dir (keys %{$V->{`Directory'}}){

    print "Options for Directory: $dir\n";

    while(($opt,$val) = each %{$V->{`Directory'}{$dir}{`OPTIONS'}}){

        print "\t",$opt, " : ", @{$val},"\n";

    }

    print "\tLimit: @{$V->{Directory}{$dir}{LIMIT}{METHODS}}\n";

    while(($key,$val) = each %{$V->{Directory}{$dir}{LIMIT}{OPTIONS}}) {

        print "\t\t$key = @{$val}\n";

    }

    print "\n";

}



# rudimentary permissions checking



$webuser = (getpwnam($V->user))[2];

opendir(ROOT,$V->server_root);

@files = grep(!/\.\.?/,readdir(ROOT));

closedir(ROOT);

foreach $f (@files){

    @s = Stat($V->server_root."/$f");

    if($s[$ST_UID] == $webuser){

        print "Warning: ",$V->server_root,"/$f is owned by ",

                $V->user,"\n\n";

    }

    if($f eq "httpd"){

        if(($s[$ST_MODE] != 0100700) or ($s[$ST_UID] != 0)){

            print "\tWarning: ",$V->server_root,"/httpd may have\n";

            print "\tpermission problems.  Recommend root ownership\n";

            print "\tand readable, writable and executable only by\n";

            print "\troot user\n";

        }

    }

}

Note that we added a few lines to the foreach loop, which loops over the <Directory> entries in access.conf, to tell us about any <Limit> directives and how they're configured. Now when we run the script against our existing hierarchy, we get the following output:

Userid: nobody

Group: nogroup

Administrator is: wmiddlet@adobe.com

Running at port: 80

Access filename: .privaccess

User Directory: public_html

Global Types:   text/html .shtml





Options for Directory: /usr/local/etc/httpd/cgi-bin

        Options : None

        AllowOverride : None

        Limit:



Options for Directory: /usr/local/etc/httpd/htdocs/test1

        Options : None

        AllowOverride : None

        Limit: GET POST

                require = valid-user



Options for Directory: /usr/local/etc/httpd/htdocs

        PerlHandler : main::handler

        AddType : text/html .shtml

        Options : Indexes SymLinksIfOwnerMatch IncludesNOEXEC

        AllowOverride : AuthConfig FileInfo Indexes Limit

        Limit:

The <Limit> options for the test1 directory are now specified.

Whos There? Configuring Client IP/Domain Restrictions

After you've configured the access rights on a per-directory basis, using either the global access.conf or directory-specific access control files, you may decide that you want to allow or deny access on a per-site basis, as well. You may, for instance, only wish to allow connections from a particular domain, possibly for specific directories. You can do this additionally within the global access.conf file using additional entries within the <Limit> directive for a particular directory.

It's important to understand how the allow/deny directives are parsed within any given <Limit> statement. You have three options to specify parse order that give you the ability to override previous deny statements with allow statements, using the order statement

order deny, allow

Using this form lets you, for instance, deny from everywhere, then allow only from a specific domain(s) or host(s), perhaps like this:

<Limit GET>

order deny,allow

deny from all

allow from .corp.adobe.com

</Limit>

Likewise, you can override allow statements with deny statements to allow everyone and then deny a few specific domains or hosts like so:

<Limit GET>

order allow,deny

allow from all

deny from .bozo.com

</Limit>

You can also treat all connections as denied, unless the host appears in either an allow or deny statement, using the following order option:



<Limit GET>

order mutual-failure

allow from .metronet.com

allow from 130.248

deny from .bozo.com

</Limit>

This option is probably the safest, because it won't allow any connections from anywhere, unless they're specifically allowed.

Running our script again now gives the following:

Userid: nobody

Group: nogroup

Administrator is: wmiddlet@adobe.com

Running at port: 80

Access filename: .privaccess

User Directory: public_html

Global Types:   text/html .shtml





Options for Directory: /usr/local/etc/httpd/cgi-bin

        Options : None

        AllowOverride : None

        Limit:



Options for Directory: /usr/local/etc/httpd/htdocs/test1

        Options : None

        AllowOverride : None

        Limit: GET POST

                require = valid-user



Options for Directory: /usr/local/etc/httpd/htdocs

        PerlHandler : main::handler

        AddType : text/html .shtml

        Options : Indexes SymLinksIfOwnerMatch IncludesNOEXEC

        AllowOverride : AuthConfig FileInfo Indexes Limit

        Limit: GET

                deny = from .bozo.com

                order = mutual-failure

                allow = from .metronet.com

But note our <Limit> directive for DocumentRoot has not limited retrievals in /cgi-bin. Because the cgi-bin directory isn't beneath DocumentRoot, it's not affected by the <Limit> statement. You'll need to explicitly limit anything outside, or above, the directory where you place <Limit> statements if that is the intent.

Another IP Restriction Mechanism: tcpwrapper

Another alternative, when you wish to restrict/verify connections from particular hosts, is to run a site-wide tool like tcpwrapper. (This will work only if you're starting httpd out of inetd.) You can get tcpwrapper from the COAST archive at

ftp://ftp.cs.purdue.edu/pub/tools/tcp_wrappers

along with plenty of other security tools, papers, and packages. Using tcpwrapper, you can configure certain actions based on the connecting site's domain, or hostname, which are vastly more powerful than the options you have with httpd alone. Many sites out on the open Internet use this tool, and it even ships with a couple of operating systems. If your site has an open pipe to the Internet, it may well be worth investigating this tool.

Whos There? User/Group Verification

Finally, you can use the http access.conf file, along with a couple of other files and tools, to configure security for a specific directory or file on a per-user or per-group basis. You'll need to create the .htpasswd file on a per-directory basis and/or a global .htpasswd file. The HTTPD::UserAdmin module is the tool of choice for manipulating passwd/group files. It knows about the various types of user and group databases that the Apache, NCSA, and even CERN servers implement, and provides you with a generic interface that hides the inconsistencies, simplifying the maintenance of the databases.

The user/group databases may take various forms and use different internal formats, depending on your preference and what tools are available on your system. The HTTPD::UserAdmin module allows you to access databases implemented as DBM files or straight text files. It also gives you the capability to talk to an SQL database and to get/put user information to an SQL database. Its companion, HTTPD::Authen, knows about the various formats of the entries in these files, including MD5 (Message Digest), as well as standard (default) DES encryption and, of course, plaintext.

The HTTPD::UserAdmin module provides you with several useful methods after you've created a new HTTPD::UserAdmin object. These include the following:

add(name,passwd) Add a new user to the database
delete(name) Delete a user from the database
exists(name) Check if the user exists in the database
password(name) Return the encrypted password for the user
list Return a list of all the usernames in the database
update(name,password) Update the user with a new password
group Create a new GroupAdmin object
lock([timeout]) Create a file lock for the database in anticipation of updating it
unlock Unlock the database
db(dbname) Select a different database



There are several examples, provided as tests in the t/ directory, that accompany the HTTPD:: module suite. You can adapt these to suit your particular needs. The simplest case might be one in which you wish to add a new user to an existing database. The following simple script lets you do this, first by checking if the specified user already exists in the database. If so, it updates the record with the new password. If not, it creates the new entry for the user.

#!/usr/local/bin/perl

require HTTPD::UserAdmin;



$path = "/usr/local/etc/httpd/conf";

$username = shift || "josie";

$password = "pussycats";



$user = new HTTPD::UserAdmin(DBType => "Text", Path => $path, Server => "apache");



if($user->exists($username)){

    print "$username already exists in the database\n";

    print "Updating with new password\n";

    $user->update($username, $password);

}

else{

    print "Adding $username to database\n";

    $user->add($username, $password);

    if($user->exists($username)){

        print "Successful\n";

    }

}

Changing Passwords from the Web

One of the most common requests I've seen for a CGI program is the one for a program that allows the Web client to change his/her password. Certainly, it's reasonable to want to be able to do this; unfortunately, it's more complicated and risky than it seems.

Because the httpd is running as the user nobody, and because the passwd file doesn't (or shouldn't) belong to that user, it's a bit of a quandary to have an exec'd CGI program change that file. There are alternatives, however. You can run a secondary server on a different port, under the www userid, whose only purpose would be to execute the CGI program that changes the user's password, for instance. Another alternative might be to implement something with SafeCGIPerl, discussed later, and the HTTPD::UserAdmin module just introduced. Coding it up is left as an exercise for the wary administrator. Be very, very careful.

Monitoring Userids

As a final note in this section, we'll add a bit more code to our ongoing example to keep track of userids in the .htpasswd file for each <Directory> entry in access.conf.

use HTTPD::Config;

require HTTPD::UserAdmin;

require "stat.pl";

$conf =  `/usr/local/etc/httpd/conf';

@files = qw(httpd.conf srm.conf access.conf);

$V= new HTTPD::Config (SERVER => Apache,

                             SERVER_ROOT => $conf,

                             FILES => [@files]);



print "Userid: ", $V->user,"\n";

print "Group: ", $V->group,"\n";

print "Administrator is: ", $V->server_admin,"\n";

print "Running at port: ", $V->port,"\n";

print "Access filename: ",$V->access_file_name,"\n";

print "User Directory: ", $V->user_dir,"\n";

print "Global Types:\t", join("\n\t\t",$V->add_type),"\n";

print "\n\n";



foreach $dir (keys %{$V->{`Directory'}}){

    print "Options for Directory: $dir\n";

    while(($opt,$val) = each %{$V->{`Directory'}{$dir}{`OPTIONS'}}){

        print "\t",$opt, " : ", @{$val},"\n";

        if($opt eq "AuthUserFile"){

            ($path = join(`',@{$val})) =~ s/^(.*)\/.*/$1/;

            print "\tCurrent users in $opt:\n";

            $users = new HTTPD::UserAdmin(DBType => "Text",

                Path => $path, Locking => 0, Server => "apache");

            #@users = $users->list;

            foreach $user (sort($users->list)){

                print "\t\t$user : ";

                print $users->password($user),"\n";

            }

        }



    }

    print "\tLimit: @{$V->{Directory}{$dir}{LIMIT}{METHODS}}\n";

    while(($key,$val) = each %{$V->{Directory}{$dir}{LIMIT}{OPTIONS}}) {

        print "\t\t$key = @{$val}\n";

    }

    print "\n";

}



# rudimentary permissions checking



$webuser = (getpwnam($V->user))[2];

opendir(ROOT,$V->server_root);

@files = grep(!/\.\.?/,readdir(ROOT));

closedir(ROOT);

foreach $f (@files){

    @s = Stat($V->server_root."/$f");

    if($s[$ST_UID] == $webuser){

        print "Warning: ",$V->server_root,"/$f is owned by ",

                $V->user,"\n\n";

    }

    if($f eq "httpd"){

        if(($s[$ST_MODE] != 0100700) or ($s[$ST_UID] != 0)){

            print "\tWarning: ",$V->server_root,"/httpd may have\n";

            print "\tpermission problems.  Recommend root ownership\n";

            print "\tand readable, writable and executable only by\n";

            print "\troot user\n";

        }

    }

}

We get a fairly nice report back that looks like this:

Userid: nobody

Group: nogroup

Administrator is: wmiddlet@adobe.com

Running at port: 80

Access filename: .privaccess

User Directory: public_html

Global Types:   text/html .shtml





Options for Directory: /usr/local/etc/httpd/cgi-bin

        Options : None

        AllowOverride : None

        Limit:



Options for Directory: /usr/local/etc/httpd/htdocs/test1

        Options : None

        AllowOverride : None

        Limit: GET POST

                require = valid-user



Options for Directory: /usr/local/etc/httpd/htdocs

        PerlHandler : main::handler

        AuthUserFile : /usr/local/etc/httpd/conf/.htpasswd

        Current users in AuthUserFile:

                bmiddlet : ztTO6y2K.3qLE

                bozo : 0Euk1WMWyXRgg

                josie : _LhTDVY/y6tPo

        AddType : text/html .shtml

        AuthName : PasswordAdmin

        Options : Indexes SymLinksIfOwnerMatch IncludesNOEXEC

        AuthType : Basic

        AllowOverride : AuthConfig FileInfo Indexes Limit

        Limit: GET

                deny = from .bozo.com

                order = mutual-failure

                allow = from .metronet.com

Of course, if your user database gets very large, then this script may produce more output than you want to see. The idea from the beginning has been that this script would run automatically via cron, having its output compared to the expected output, possibly from the day before, then sending on only the differences for your inspection.

CGI Security

If you use CGI scripts at all in your Web server, you'll need to take special care to make sure that they're safe. There have been a number of break-ins and other security violations due to incorrectly configured or carelessly written CGI programs. It will pay to be completely familiar with all of the issues and precautions regarding using the CGI.

General Considerations: Dos and Donts

Because the CGI modules are introduced later in this book, you will not learn how they specifically deal with the potential security holes at this time. Most security holes introduced through the use of CGI scripts are unintentional; the following section discusses some of the most pop- ular ones. Never Trust Anything Sent As Input This is the first rule of safe CGI programming. Just because the server is running as the nobody user doesn't mean that malicious activities can't be carried out. If your input data is being processed in any way, even just writing it onto a file, the potential for an attack exists. The following list illustrates some of the most important considerations when creating CGI programs and handling input to forms:

Variable Length A form may prompt for a variable specifying a MAXLENGTH, but the sender may potentially send a form variable thousands of bytes long. This problem was, in fact, a CERT Announcement against an early version of the NCSA httpd. Be sure that you're not running httpd with a version number earlier than 1.4 to avoid the hole this created. Also make sure that your script itself can handle such a long submission properly.
Shell MetaCharacters The input from a form could potentially include shell meta characters or other special characters or commands that could get you in trouble.



CAUTION:

Never, under any circumstances, pass user input to the shell in raw form; in fact, you shouldn't have to ever invoke a shell process at all if you're doing things right. If you use the CGI and Mail modules to process the form and perform the desired action(s), this is handled for you automatically. Even so, it never hurts to apply your own standard to the returned values from the CGI parsing process just to be sure.


Selection Lists/Variables The value input for some selection list item, or any variable for that matter, may not be among the ones that you've specified. If you have a default action, make it do nothing or warn for any unexpected value.
Hidden Variables Don't assume that just because you define a variable to be hidden in your form, it can't be seen by any means. The same goes for Netscape cookies: The malicious client could send something to you as a hidden variable or cookie that you didn't give the client in the first place.




In general, as has been mentioned, invoking a shell with any form input should be avoided. It is very easy to do the right thing with user input when you use the CGI modules to process the form. Alas, there will always be the need for the quick hack, sometimes due to urgency or immediate need for results, and other times due simply to laziness. A quick review of the no-no's is thus in order:

  1. Never invoke the shell directly. Perl provides several techniques for getting past the shell directly to the desired program, and these should be utilized. For instance, you could invoke sendmail by using the following:
    system("/usr/lib/sendmail -t $address < $input_file");
    
    

    But doing it this way is a big mistake, especially when the $address and/or the $input_file variables may come from the form itself. It's much better to invoke sendmail like this:

    open(SENDMAIL, "/usr/lib/sendmail -t|");
    
    

    Then print directly on the SENDMAIL pipe, first the header, followed by the body of the mail, and close it. However, it can't be stressed enough that even this could get you in trouble. You should always use the Mail::Send module's methods to send mail. Another technique for getting past the shell in system() is to use the somewhat obscure technique of quoting each parameter to the command line:

    system "/usr/bin/sort", "$input_file";
    
    

    This syntax invokes the sort command directly instead of passing the arguments through the shell. Note how we explicitly specified the full path to the sort command. Never rely on the $PATH variable to figure out which command you really mean. This is how Trojan horses get inside the gates. Also, don't forget that system() isn't the only way to invoke a shell with Perl. There are also the backtick operators, fork(), exec(), and pipe(), and even glob() and implicit glob via <'command'>. Be very careful with quoted eval"" statements, as well.

  2. Always turn tainting on with the -T option to Perl. This option automatically renders arbitrary input as "tainted" and unusable in subprocess invocations until it is untainted. Of course, it's quite easy to untaint variables, but if you turn on -T in the shebang line of your Perl script, you might catch yourself in a moment of weakness when you've forgotten to escape some arbitrary input.

  3. If you insist on parsing your own input, there are basically two policies, the first being that anything not permitted is forbidden. The regular expression
    $address =~ /^[\w@\.\-]+$/;
    
    

    allows only a specific class of characters, which should match an e-mail address. If, when your $address variable doesn't match this regexp, your programs errors, you're enforcing this policy. The other alternative is to permit anything that isn't forbidden. If, for instance, you check your $address for any shell meta-characters with the following regexp:

    $address =~ /([;<>\*\|'&\$!#\(\)\[\]\{\}:'"])/;
    
    

    and then allow anything that doesn't match, you're enforcing the less-restrictive policy, specifying only what is unsafe. As you might expect, the former is preferred. And again, using the CGI modules handles this for you.

  4. Finally, always be sure to check and double-check before allowing arbitrary input to be used for a pathname. Be especially vigilant for the old `..' operator, which can be used to access files down from the htdocs directory and possibly into the /etc directory, where the passwd file lives.


    Using the various CGI modules to create and especially process your form input will help you to avoid these traps. You have additional power to prevent attacks and limit the capabilities of the client using several other powerful Perl modules that aren't discussed elsewhere in this book. Let's take a look at these now.

Tools for Executing CGI Safely

If you're using Perl as your CGI scripting mechanism, there are a number of new tools and modules you can use to assure that your CGI scripts operate correctly and safely. These modules are consistently maintained and updated, and the authors are fully aware of all of the potential problems that face the Webmaster when introducing the CGI risk to his or her Web. We'll be introducing these tools and modules in Chapter 5, "Putting It All Together."



NOTE:

It's fairly trivial to write a Perl script to perform a specific task as a CGI if you're an experienced Perl programmer. So why bother learning how to use a specific module to perform a task if you can hack up something offhand? The risks of doing this probably are in proportion to the experience of the scriptor and his/her familiarity with the CGI-related risks, but in the long run, the added value of learning and using the appropriate module will definitely outweigh the bother of learning to use it. Hopefully, this will become evident to you as you read this book.


The Safe Module

The Safe module is relatively new to Perl5 but is already part of the default distribution. It provides you with the capability to create a "compartment" where only certain Perl operations can be executed. If a script attempts to execute an operation that is disallowed or hasn't been specifically allowed, the operation will be trapped, and the error will be found in $@, just as with eval(), looking something like

open trapped by operation mask

to indicate that the open() operator is not allowed within the compartment.

You might use the Safe module to create a compartment where the script can never execute a command that causes the UNIX shell to be invoked, for instance, by masking out the opcodes that correspond to system(), '' (backticks), exec(),fork(), and glob(). Masking these oper- ations is the default when creating a new Safe compartment.

The Safe module, as of Perl5.003, uses the Opcode module to set up its compartment and the masks for each of the opcodes that are considered unsafe in the typical insecure environment. The Opcode module relies on the fact that Perl code is compiled into an internal format before it is executed. Within this compiled internal format, is of the operations Perl can execute is reduced to a numerical value. If a mask has been put in place for any given operation's opcode, for instance, open(), the compilation will fail if the open() statement is found anywhere in the Perl code. The Opcode module is not usually used directly but should be understood by those who will be setting up Safe compartments.



CAUTION:

The Opcode module released with Perl5.003 has a bug that can allow the script to access the shell through the Glob operation. It's extremely important that you apply the security patch to the Opcode module if you're using a version older than Version 1.02. Or obtain the latest version from Tim Bunce's CPAN directory.
In general, within the Opcode module, each operation you want to mask can be specified by its canonical name, as specified in Opcode.h from the Perl source. Most of the methods from the Opcode module take a list of operators as parameters. The list's elements can consist of an individual opname, which specifies an individual operator like open, or an optag, preceded by a colon :, which refers to a group of operators. The Opcode module specifies several optags, for convenience and correctness, that can be used to allow or mask out groups of operations. The methods will also accept negated opnames or optags, prepended with a !, which will remove the specified opname or optag from the list of ops masked up to that point. The Safe module sets up a compartment by default that allows the operations corresponding to the optag :default, which consists (as of this writing) of the following optags:

:base_core The core operations required for Perl to start up, compile a script, and run. This set lets you do the very basic things with a script, like assignments, addition, subtraction, multiplication, keys, values, split, and others.
:base_mem These add the capability to repeat, concatenate, and join, as well as create anonymous data structures. They're not part of the :base_core opset because they can be used to implement a resource attack on a machine, possibly consuming all of its memory.
:base_loop These ops allow the script to use loops. They can be used to chew up all of the CPU time on a system.
:base_io These ops enable filehandle input. They are considered safe only when the filehandle exists before the compartment is created and is shared into the compartment. STDIN and STDOUT are shared into every Safe compartment by default, but that's it. Ops include read, readdir, print, seek, and others.
:base_orig These ops are still under consideration but are included in the safe compartment at this time. Ops like sprintf, crypt, select, getppid, gmtime, and others are among the hodge-podge included under this optag. This group is especially subject to change.




NOTE:

Note that the Opcode module instructs you not to rely on the definition of the :default tag, or any other tag, for that matter. Always check any new release to be sure that the operations you expect to be included or excluded truly are or aren't.
All other operations are disallowed within the default Safe compartment. The only variables that are shared into the default Safe compartment include $_, @_, and %_, along with the filehandles STDIN and STDOUT. They are shared into the default compartment upon invocation of the new() method for Safe, along with all the symbols that exist in the main:: namespace at the time the compartment was created. If you wish to enhance the capabilities of a given Safe compartment, you can do so with the methods provided within the Safe module. The other optags defined in the Opcode module include the following:

:base_math Math ops that can generate a floating point exception, like atan2, sin, cos, exp, log, and sqrt. If you allow this group, you should also set up a $SIG{FPE} handler.
:filesys_read This group includes the stat, lstat, and readlink operators. In general, access to information about the filesystem, even just the information from stat(), can provide insight on potential holes in security and should be allowed with caution.
:sys_db The large set of get* calls to do lookups into the system database files, like passwd or hosts. gethostbynam(), getpwuid(), and so on, belong to this group. Access to the system databases is generally a no-no but may be required to do anything truly useful. Use with caution.
:browse A handy tag name for a reasonable set of ops beyond the :default optag. Includes the :default, :sys_db, and :filesys_read tags.




CAUTION:

The rest of this list of optags can be considered to be extremely dangerous to allow within a truly Safe compartment. Don't allow these ops in your scripts to be executed within the Safe compartment without understanding exactly what you're doing and whom you're doing it to. Caveat scriptor.

:filesys_open These ops include the open(), close(), sysopen(), opendir(), and other operations, which allow direct access to files in the system with a Perl program.
:filesys_write Includes link, unlink, symlink, truncate, mkdir, rmdir, chmod, and chown operations, among others.
:subprocess Backticks, system, fork, wait, and glob are included within this group. Possibly the most dangerous opcodes from a security standpoint.
:ownprocess exec, kill, exit.
:others msgctl and other SysV shared memory routines.
:still_to_be_decided chdir, flock, ioctl, socket, getpeername, bind, and the rest of the Berkeley networking calls, as well as sleep, alarm, sort, pack, and caller. This optag is quite unstable and should not be relied upon. There are ops here that may be retagged or removed in the future for various reasons.


Any of these optags, or any of the other opnames that aren't defined in the default Safe compartment, can be permitted via the permit() method. After you've set up the compartment the way you wish, user code can be executed within it via the reval() method, or a script can be compiled and executed via the rdo() method. You should become fully acquainted with the Safe module, in its intents and workings, before using it. This also goes for the Opcode module. Their documentation is embedded into the modules themselves as POD. SafeCGIPerl Now that you understand how the Safe module works, you might think that you could create a program that would set up a Safe compartment and then execute arbitrary Perl code for a given user. After all, if only the :default opset is allowed, what can go wrong? Right? Well, theoretically, yes, but in practice, the :default optag is just too restrictive to do anything useful within a given CGI script. So just how do you set up a scheme whereby the arbitrary script can be executed as a CGI? One way is to use SafeCGIPerl. SafeCGIPerl is a package that utilizes the Safe module specifically to provide a means to run CGI scripts under a given user's UID, and provide a restricted environment while giving the user's code some additional leeway to perform useful tasks like sending mail. In order to use SafeCGIPerl, you must first obtain the package from a CPAN site (available in Malcolm Beatties' CPAN directory), and then, assuming you're running Perl5.003 or later, apply the patch to use later versions of the Safe module. Next, build the safeperl executable and install it as a root suid program into your cgi-bin directory and install the cgiperl script into /usr/local/bin. When you're finished, your users will be able to create a $HOME/cgi/bin directory in their home directory and place scripts there that will be executed by safeperl when the URL of the form

http://Your.Site/cgi-bin/safeperl/username/scriptname

is received by httpd. The SafeCGIPerl scheme is quite comprehensive but also provides methods for performing rudimentary tasks that aren't available within the standard Safe compartment. SafeCGIPerl consists primarily of two components:

safeperl The suid-root executable that performs most of the ground work and then executes cgiperl.
cgiperl A Perl script that sets up a Safe compartment, along with some additional functionality, and then executes another (arbitrary) script running under a specified user id using the rdo() method from the Safe module.



Let's take a look at how SafeCGIPerl works.

  1. First, safeperl parses the URL to be sure that the username is valid. It does this by first escaping any unwanted characters from the string, then parsing out the username and calling getpwnam() with the string it gets. If the username isn't valid, it exits with an error. If the username is valid, it logs the request and changes to the UID specified and to that user's ~/cgi/bin directory.

  2. Once the UID and directory have been set, the safeperl executable sets up an environment using the UNIX limit() system call, which explicitly restricts some crucial system-level parameters for the process (Perl) which it's about to exec. The parameters that are restricted are CPU time, niceness, coresize, data-segment size, and resident-memory size. Doing this implicitly avoids many of the risks that can be introduced by allowing Perl ops like looping or memory-growing by specifying in advance the maximum allowable amount for these parameters.

  3. Finally, the safeperl binary execs cgiperl with the desired scriptname expected now to be in the current working directory. The script is then executed by cgiperl within a Safe compartment. This implies, of course, that the script can't do just any old thing, but it does give the user some additional capabilities, including a subroutine for sending mail and capability to read and write files within a particular directory, along with the standard operators that are part of the Safe :default optag.

In general, SafeCGIPerl is a vastly preferable alternative to allowing users to run arbitrary scripts within their own Web space via the .cgi extension. It is not, however, a foolproof solution. As always, read the documentation carefully and provide the same documentation, along with any other warnings appropriate, to your users before telling them to utilize this tool. CGIWrap While not specifically a Perl package, CGIWrap is another tool you can implement on a site-wide basis to allow your users the freedom to create and execute their own CGI programs. It does not, however, provide the opcode masking that SafeCGIPerl does, nor any kind of Safe compartment. All it does, basically, is ensure that a given user's CGI programs are run under his/her own UID. The danger to the user is still quite prominent, unless he/she is very savvy about what to do, and what not to do, with CGI programs in general. Further, if an unsavvy user allows a Trojan horse to be placed in his/her path, it can endanger the security of the entire system.

Transaction Security

Some types of transactions, especially credit-card purchases, may require that you assure the security of the transaction itself. There are numerous means for unsavory individuals to snoop into what you're sending or receiving from the other end, including, but not limited to, the following:

Spoofing The client can trick your server into believing that the request or post that it's sending is from some other site. This is known as IP and/or DNS spoofing. Your server may respond believing that the client is "Trusted" when it isn't.
Sniffing In some cases, it's possible for an unsavory individual to snatch packets as they zip by on the wire. Especially with the newer cellular modems, unsecured phone lines, and so on, it's becoming easier all the time to see everything coming and going to a client or server.
Traffic Analysis Using sampling techniques on the packets (a form of sniffing) or, more commonly, the httpd's logfiles, an individual (possibly unsavory) can learn about the nature of the transactions that your site processes. This may be useful, for instance, in analyzing the competitive level of your site by a site that provides the same services or products.

In each of these cases, the risk can be alleviated (or greatly reduced). In the cases of spoofing and sniffing, the preferred technique is to use data encryption, or signed data for the transaction. When the receiving end gets what your server sends them, they must have the appropriate key to decrypt and make use of it. In the case of traffic analysis of the data files, assigning the file permissions on the directory, logs, and the files themselves is the preferred technique. The logs themselves can be encrypted for permanent archival.

Nowadays, most commercially available servers and their respective clients implement encrypted transactions via some, usually proprietary, means. There's also a means to build encryption into the NCSA httpd and Apache httpd servers. See the documentation for these servers or the references listed at the end of this chapter in the section entitled "Further Reading" for details on how to do this.

Simplified PGP Transactions Using the PGP Module

In the simplest case, you may wish to implement an encrypted transaction simply and quickly, but not necessarily through the browser or server. For instance, when I wanted to subscribe to "The Perl Journal," I was asked to encrypt my credit card number using the PGP public key for TPJ, then mail the encrypted credit-card number to them. What then? In this case, there is again a Perl module available specifically for this purpose. It's called the PGP module, and it provides a nice Perlish interface to the PGP binary, available for noncommercial users from MIT and commercial entities through ViaCrypt. (We're not going to get into the details of what PGP is, or how it works, here. For that information, you can consult the "Applied Cryptography" text mentioned at the end of the chapter, along with the documentation that comes with PGP itself.)

The PGP module provides the user with a number of convenient methods, once the base PGP object has been created with the new method, as usual. The following is a listing of the most important methods:

Sign Take a file or scalar variable and sign it with the user's secret key.
Encrypt Produce and encrypt a file or variable from an unencrypted variable or file using a public key(s).
Decrypt Decrypt a file or text variable by using a secret key.
Keyring::new Produce an object that has elements corresponding to each key on a user's keyring. This object is then useful for checking the completeness and trust level of any given key.
Keyring::Add_Key Add a new key to the user's keyring.
Keyring::Remove_Key Remove a key from a user's keyring.


We'll be using the 0.3 Version of the PGP module for the following example. Note that the PGP module is still somewhat in a state of flux but is stable enough for simple projects. The author, as is common to all module authors, is amenable to bug fixes or appropriate changes. The latest version can be obtained from the CPAN. (I had to apply some fixes to the 0.3 version to get it to work. The fixed version is available in the sample code area on the CD-ROM.)

Now suppose, as was previously mentioned, that we wanted to encrypt a simple document and mail it to someone by using that person's public key to encrypt the document, to be sure that only that person could then read it. Naturally, you could use the PGP binary directly. We won't get into that, because the PGP module provides much more flexibility. The following script will prompt you for the receiver's public key and your secret key in order to encrypt a file. Then it will mail the encrypted file off to the receiver, prompting for his/her e-mail address or using his/her public key as the default mail address.

#!/usr/local/bin/perl

# simple script to send pgp encrypted mail - wmiddlet@best.com



use PGP::Pipe;

use Mail::Send;



$pgp = new PGP::Pipe;



# get the users pgp password, echo off

print("Type your PGP Password (echo disabled) :\n");

system("stty -echo");

chop($PGP_Password = <STDIN>);

system("stty echo");



# get the public keys for the recipients

print "Now enter the user ID(s) to encrypt with: ";

chop($TO_id = <STDIN>);



# use the public key(s) as the email address by default

print "Send the mail to: [$TO_id] ";

chop($To = <STDIN>);

($To = $TO_id) unless (length($To));



# Get a subject line

print "Enter the subject : ";

chop($Subject = <STDIN>);





# prompt for the filename to send

print "Enter the file to send : ";

chop($file = <STDIN>);



# create the encrypted document and stuff into a variable

$encrypted =  $pgp->Encrypt(Password => $PGP_Password, File => $file,  TO_id => $TO_id, Clear => 1, Armor => 1);



# Create new Mail::Send object, using the address specified

$mail = new Mail::Send Subject => $Subject, To => $To;



# print the encrypted message on the mail body, and send.

$fh = $mail->open;

print $fh $encrypted;

$fh->close;

Pretty simple, eh? We used the Mail::Send module to deliver the message here, which won't be formally introduced until Chapter 7, but you get the idea. Encryption really isn't a deep, dark secret known only to the wizards. Anyone can use it, and Perl makes it even easier. Client Security The client-side of the Web transaction has analogies to some of the preceding potential problems, as well. The client may need to be assured that the server it is receiving its data from is who it says it is (spoofing), and that the data itself is truly what was sent from the server. Additionally, the client may be asked to execute some code, in the form of a Java applet, Java script, TCL script, or Perl script (discussed in Chapter 15), or some other "Helper Application."

In general, it's not recommended to enable the embedded scripting features within a given browser if you have secure or proprietary data anywhere within access of the user running the browser. Further, it's risky to enable certain helper applications to automatically read or run spreadsheets or other word-processor documents. It's a known fact that such documents may contain macros that can cause viruses to infect your system.

It's always safer to save a given document, script, or whatever to disk by default, then (minimally) run some virus-detection software against it and visually inspect it, if it's text, before assuming it's safe. Even then, if you are extremely cautious, assume that it may contain a virus or Trojan horse that hasn't even been discovered yet.



CAUTION:

Always use the latest updates of any virus detection software you run. This probably goes without saying, but new viruses are always in the process of being implemented, then discovered, and then dealt with in any particular virus-detection software.


Other Considerations

The topics that follow have very little to do with Perl. They are included for the sake of completeness in this chapter.

Dont Run Unnecessary Daemons

If you're not going to be using a particular daemon, either from inetd or standalone, disable it; sendmail, tftp, systat, netstat, and any of the given r-servers, rexd, rquotad, and so on are possible candidates.

Sharing Documents Between Serversftpd and httpd

It's convenient, and often a requirement due to limited space, to serve the same hierarchy from httpd and ftpd. Some sites still run the gopherd as well, usually on the same files. Doing this is okay, as long as you take care not to allow uploaded files to be served from httpd as CGI programs. Be sure that if you do allow uploads via ftpd, the upload directory is completely hidden from httpd, using file permissions, ownership/uid, and access.conf configuration options.

Running httpd in the chroot(2) Environment

You can make significant progress toward a secure environment by running your httpd in a chroot'd environment. When you force a process to run chroot, it treats the hierarchy beneath the chroot'd directory as its entire filesystem and won't be able to access anything beneath it. This implies that the DocumentRoot must be set up as a complete, minimal filesystem, including shared libraries and possibly certain devices, to allow the httpd process to run. Implementing this is nontrivial. You should consult the documentation for your operating system to perform it correctly. If you're really worried about the security of the rest of your filesystem, however, it's a very reassuring alternative.

Privacy and Server Logs

While it may not be directly related to the security of your server, the privacy of those who access your site is something else to be mindful of and take steps to guard. If you use a commercial server, which also encrypts the logs, you're all set. Otherwise, be careful with the permissions on the logs directory, as previously mentioned. Also, find yourself a good log-analysis tool and use it frequently, then truncate the logfiles. It's inappropriate to use the logfiles for any purpose, other than overall traffic analysis, error-checking, and usage statistics in the overall sense.

Problems with Specific Servers

There are a number of server-specific issues that you may be affected by if you run any given server. These are, by nature, in constant flux, and it would be foolish to attempt to summarize them. The only way to keep track of these is to monitor the newsgroups, FAQs, and mailing lists related to the Web and specifically related to the server you choose to run. There are also general security groups and mailing lists that you can follow that deal with security issues in general. Please note, however, that these groups, and in fact most newsgroups, are not specifically intended for Web users' questions. Have a little netiquette and don't spam these groups with your questions. Take the time to read their FAQs and follow the discussion for a while before posting questions if appropriate.


CAUTION:

If you run your server under Windows NT, it's generally a very bad idea to place the perl.exe executable within the cgi-bin directory. Never, ever, do this under any architecture. No matter how frustrated you become with trying to figure out how to get the File Manager's associations to work with specific extensions, it's just not worth the risk. There has been a CERT announcement about this, too. For more information, see ftp://ftp.cert.org/pub/cert_advisories/CA-96.//.interpreters_in_cgi_bin.


Monitoring Filesystem Changes, Network Security, and Analyzing Password Security

There are three basic tools to enhance the security of your system by monitoring filesystem changes and checking for bad passwords. These tools have been in use for a long time and are freely available. We recommend that you use them all on a regular basis. Monitoring the Filesystem First, for monitoring the filesystem, there are two primary tools. The first is called TripWire. It's a suite of C programs and bourne shell scripts that analyze the critical files on your system and produce a database. Then, each time you run the tool after the database has been created, it compares the current state of the files to the stored parameters for size, ownership, permissions, timestamps, and checksums. If anything changes, you get a report of exactly where the change occurred, when it happened, and what changed for any given critical filesystem entity that you have set up to monitor. You can obtain TripWire's latest version at

ftp://coast.cs.purdue.edu/pub/COAST/TripWire

The other tool commonly used to monitor the filesystem is called COPS (Computer Oracle and Password System). COPS is a suite of Perl scripts that traverse the filesystem, looking for SUID programs or scripts, system directories and files that are world writable, and other potential holes. COPS also makes a database that stores the current status of all important files it finds, compares any given run against this database, and reports back about a large number of potential problems. COPS also provides rudimentary checking for bad passwords, user's home directories and login files, anon ftp setup, unrestricted tftp, and even timestamps of key files that have been included in past CERT advisories, along with the Kuang expert system, which takes a set of rules and tries to determine if your system can be compromised. COPS is available at

ftp://ftp.cert.org/pub/tools/cops

Monitoring System Password Security There are several steps that must be taken in order to implement a plan for preventing bad passwords. The first, and most important to implement, is to insure that the user cannot create a bad password. There are a number of potential algorithms that should be checked before allowing the user to change or create the password. One tool that is widely used to do this replaces the /bin/passwd program with a suid wrapper that invokes a Perl script called anlpasswd. There are also many others. The important thing to realize is that you must ensure that the user cannot select a "bad" password before implementing the other components of a password security plan.

The next step in password security is to run a program called Crack against the /etc/passwd (or its shadow) database. Crack is a CPU intensive program that runs, often for days at a time, attempting a large number of algorithms to guess the user's password(s). Once complete, the administrator informs the user that he/she needs to change his/her password if it was guessed--but not until step one is in place, or the purpose is defeated, because the user can simply choose another bad password. Crack can also be obtained from the CERT site:

ftp://ftp.cert.org/pub/tools/crack/

Finally, a password aging scheme is sometimes used. Many users find this irritating and frustrating, to memorize one difficult password, only to have to forget it and memorize a new one a few months later. Password aging is important, though, especially if your site has proprietary or other secure information within its hierarchy. Consult your administrative documentation for more details on how to implement password aging on your particular architecture. Monitoring Network Security General network security can be tested using the "Satan" suite of programs; written in Perl and rpc/C, it is a suite of programs to test most or all of the potential network holes. Satan is available at most security-related sites, including: ftp://coast.cspurdue.edu/pub/tools/unix/satan.

Further Reading

There are always new techniques to learn, and new risks to consider, in the matter of server security. It's a never-ending vigil you must keep to prevent unwanted access on your machine. Fortunately, there are plenty of other administrators out there who also wish to prevent unwanted access and are actively working to make available their shared experiences in the form of books, FAQs, programs, articles, and postings from which you can learn. Some of the most important resources are the following:

http://www-genome.wi.mit.edu/WWW/faqs/

Dr. Stein has also published the definitive guide for managing a Web service, called, appropriately enough, How to Set Up and Maintain a World Wide Web Site: The Guide for Information Providers, ISBN 0-201-63389-2. This is highly recommended as a complete reference. For an extended, definitive reference on the science of cryptography, including examples, algorithms, and a detailed study of most of the major methods available today, see Applied Cryptography, Schneier, 1996 (Wiley Press), ISBN 0-471-59756-2.