Mod Perl Icon Mod Perl Icon Protecting Your Site


[ Prev | Main Page | Next ]

Table of Contents:


The Writing Apache Modules with Perl and C book can be purchased online from O'Reilly and Amazon.com.
Your corrections of either technical or grammatical errors are very welcome. You are encouraged to help me to improve this guide. If you have something to contribute please send it directly to me.

[TOC]


An Importance of Your site's Security

Lets state it, your site or service can easily become a target for some Internet ``terrorists'', for something that you said, because of success your site has, or for no obvious reason. Your site can be broken in, the whole data can be deleted, some important information can be stolen, or a much easier the site can be turned into a useless with a _simple_ Services Deny Attack. What can you do about it? Protect yourself! Whatever you do, your site will be still vulnerable as long as you are connected to the network. Cut the connections down, turn off your machine and put it into a safe - now it is more protected, but very useless.

Let's first get acquainted with security related terminology:

Authentication

When you want to make sure that a user is the one he claims to be, you generally ask her for a username and a password, right? Once you have both: the username and the password, you can validate them in your database of username/password pairs, and if both match - you know that user has passed the Authentication stage. From this moment on if you keep the session open all you need is to remember the username.

Authorization

You might want to let user foo to access to some resource, but restrict her from accessing to another resource, which in turn is accessible only for user bar. The process of checking access rights is being called Authorization. For Authorization all you need is a username or some other attribute you authorize upon. For example you can authorize upon IP number, for example allowing only your local users to use some service. Be warned that IP numbers or session_ids can be spoofed, and that is why you would not do Authorization without Authentication.

Actually you are familiar with both terms for a long time - when you telnet to your account on some machine you go through a login process (Authentication). When you try to read some file on your file systems the kernel checks first the permissions on this file. (you go through Authorization). That's why you could hear about Access control which is another name for the same term.

[TOC]


Illustrated security scenarios

I am going to present some real world security requirements and their implementations.

[TOC]


Non authenticated access for internal IPs, but authenticated by external IPs

If you run an Extranet (Very similar to Intranet but partly accessible from the outside, e.g. read only) you might want to let your internal users an non-restricted access to your web server, but if these users are calling from the outside of your organization you want to make sure these are your employees.

First one is achieved very simply using the IP patterns of the organization in a Perl Access Handler in an .htaccess file, which consequently sets the REMOTE_USER environmental variable to a generic organization's username, so that certain scripts which rely on REMOTE_USER environment variable will work properly.

The second one should detect that the IP comes from the outside and user should be authenticated, before allowed in.

As you understood this is a pure authentication problem. Once user passes the authentication, either bypassing it because of his IP address, or after entering correct login/password pair, the REMOTE_USER variable is being set. Now having it set we can talk about authorization.

OK let's see the implementation. First we modify the <httpd.conf>:

  PerlModule My::Auth
  
  <Location /private>
    PerlAccessHandler My::Auth::access_handler
    PerlSetVar Intranet "100.100.100.1 => userA, 100.100.100.2 => userB"
    PerlAuthenHandler My::Auth::authen_handler
    AuthName realm
    AuthType Basic
    Require valid-user
  </Location>

Now the code of My/Auth.pm:

    sub access_handler {

        my $r = shift;

        unless ($r->some_auth_required) {
                $r->log_reason("No authentication has been configured");
                return FORBIDDEN;
        }
        # get list of IP addresses
        my %ips = split /\s*(?:=>|,)\s*/, $r->dir_config("Intranet");

        if (my $user = $ips{$r->connection->remote_ip}) {

                # update connection record
                $r->connection->user($user);

                # do not ask for a password
                $r->set_handlers(PerlAuthenHandler => [\&OK]);
        }
        return OK;
    }
    
    sub authen_handler {

        my $r = shift;

        # get user's authentication credentials
        my ($res, $sent_pw) = $r->get_basic_auth_pw;
        return $res if $res != OK;
        my $user = $r->connection->user;

        # authenticate through DBI
        my $reason = authen_dbi ($r, $user, $sent_pw, $niveau);

        if ($reason) {
                $r->note_basic_auth_failure;
                $r->log_reason ($reason, $r->uri);
                return AUTH_REQUIRED;
        }
        return OK;
    }
    
    sub authen_dbi{
      my ($r, $user, $sent_pw, $niveau) = @_;

      # validate username/passwd

      return 0 if (*PASSED*)
        
      return "Failed for X reason";

    }

Either implement your authen_dbi() routine, or replace authen_handler() with any authentication handler such as Apache::AuthenDBI.

access_handler() sets REMOTE_USER to be either userA or userB according on the IP, if non matched PerlAuthenHandler will be not set to OK, and the next Authentication stage will ask the user for a login and password.

[TOC]


Authentication code snippets

[TOC]


Forcing re-authentication

To force authenticated user to reauthenticate just send the following header to the browser:

  WWW-Authenticate: Basic realm="My Realm"
  HTTP/1.0 401 Unauthorized

This will pop-up (in Netscape at least) a window saying that the Authorization Failed. Retry? And an OK and a Cancel buttons. When that window pops up you know that the password has been cleared. If the user hits the Cancel button the username will also be cleared, and you can have a page that will be output written after the header if this is a CGI (or PHP, or anything else like that). If the user hits the OK button, the authentication window will be brought up again with their previous username already in the username field.

In Perl API you would use note_basic_auth_failure() method to force reauthentication.

Since the browser's behaviour is in no way guaranteed, it also may not work, and that should be noted.

[TOC]


OK, AUTH_REQUIRED.and FORBIDDEN in Authentication handlers

When your authentication handler returns OK, it means that user has correctly authenticated and now $r-&gt;connection-&gt;user will have the username set for all the subsequent requests. For Apache::Registry and friends, where the environment variables setting weren't turned off, an equivalent $ENV{REMOTE_USER} variable will be available. Password is available only through Perl API with help of get_basic_auth_pw() method.

If there is a failure, returned AUTH_REQUIRED flag will tell the browser to pop up an authentication window, to try again, unless it's a first time. For example:

   my($status, $sent_pw) = $r->get_basic_auth_pw;
   unless($r->connection->user and $sent_pw) {
       $r->note_basic_auth_failure;
       $r->log_reason("Both a username and password must be provided");
       return AUTH_REQUIRED;
   }

Let's say that you have a mod_perl authen handler, where user credentials are checked against a database, and it either returns OK or AUTH_REQUIRED. One of the possible authentication failure case might happen when the username/password are correct, but the user's account has been suspended temporarily.

If this is the case you would like to make the user aware of this, through a page display instead of having the browser pop up the auth dialog again. At the same time you need to refuse the authentication, of course.

The solution is to return FORBIDDEN, but before that you should set a custom error page for this specific handler, with help of $r-&gt;custom_response. Something like:

  use Apache::Constants qw(:common);
  $r->custom_response(SERVER_ERROR, "/errors/suspended_account.html");
   
  return FORBIDDEN if $suspended;

[TOC]


The Writing Apache Modules with Perl and C book can be purchased online from O'Reilly and Amazon.com.
Your corrections of either technical or grammatical errors are very welcome. You are encouraged to help me to improve this guide. If you have something to contribute please send it directly to me.
[ Prev | Main Page | Next ]

Written by Stas Bekman.
Last Modified at 09/25/1999
Mod Perl Icon Use of the Camel for Perl is
a trademark of O'Reilly & Associates,
and is used by permission.