1 Session management and maintaining state

Session management or state maintenance poses a challenge for any server-side web language. This is because, in contrast to stand-alone applications, server-side applications transfer data between servers and clients. A server-side application does not have full control over the data at the client's computer. A client can delete, modify or add to any data. Therefore client-data cannot be trusted.

Sessions are created to minimise the risk of data corruption. All user data is stored on the server and associated with a session ID. Data retrieval is based on the session ID. A security check must be performed on any data, which a user enters, before the data is stored on the server. It is not necessary to perform security checks on data that is retrieved from the server. This is in contrast to CGI applications without sessions: in that case a security check must be performed on user data every time the data is used!

The session IDs should be generated by the server and should be long and complicated enough to deter users from manual tampering with the IDs. Session IDs are passed from page to page either

  • by using hidden HTML form text or
  • by using the query string or
  • by using cookies (on the client computer).

    The user data, which is associated with a session ID can be stored on the server

  • in a file
  • in a database.

    This approach reduces the number of security checks that must be performed on user data. Because users can modify their cookies (or disallow cookies) and can modify hidden HTML form text or query strings, it is possible for a hacker to break into another user's session. This problem affects all server-side web languages, not just Perl. For highly sensitive applications, other measures, such as encryption (SSL) and password protection must be added.

    There are several different libraries available for Perl which provide session management. An example is CGI::Session.

    1.1 Hidden Text - Exercise

    1) Create a form that asks a user for his/her name and some comments. Then create two CGI scripts that create a response. The first script displays the information which the user has submitted ("Hello $name. These are your comments: $comments") and asks the user whether he/she really wants to submit the information. The second script is invoked by the first one and displays "Thank you $name. Your comments have been submitted: $comments".

    To implement this add a form and hidden tags to the first CGI script, such as

    <input type='hidden' name='hiddenname' value=\"$name\">
    <input type='hidden' name='hiddencomments' value=\"$comments\">

    The second CGI script retrieves these strings via

    my $name = param('hiddenname');
    my $comments = param('hiddencomments');

    1.2 Cookies - Exercise

    2) Use a cookie instead of hidden text in the previous exercise.

    The following code in the first CGI script sets a cookie.

    $cookiedata = $name."|".$comments;
    $cookie = cookie(-name=>'nameandcomments',-value=>"$cookiedata", -expires=>'+1h');
    print header(-cookie=>$cookie);

    The second CGI script retrieves the cookie similarly to retrieving parameters:

    $cookiedata = cookie(-name=>'nameandcomments');
    ($name, $comments) = split(/\|/,$cookiedata);

    1.3 Session management with CGI::Session

    3) Use HTML::Template and CGI::Session for the same task as in the two previous exercises. The framework for the code for the first CGI script is below:

    #!/usr/local/bin/perl
    use CGI qw(:standard -debug);
    use CGI::Session;
    use HTML::Template;
    ########################################################################
    my $template_text1 = <<EOS;
    # HTML for an error message if security problems
    EOS

    my $template_text2 = <<EOS;
    # HTML for the response if no security problems
    EOS
    ########################################################################
    my $cgi = new CGI;
    my $session = new CGI::Session(undef,$cgi,{Directory=> '/home/yourdir/tmp'});
    $cookie = $cgi->cookie(CGISESSID => $session->id );
    print $cgi->header(-cookie=>$cookie);
    my $template1 = HTML::Template->new(scalarref =>\$template_text1 );
    my $template2 = HTML::Template->new(scalarref =>\$template_text2, associate => $cgi);
    ########################################################################
    if ( check the security of the data) {
    print $template1->output(); }
    else {
    $session->save_param($cgi);
    print $template2->output(); }
    }

    Notes:

  • /home/yourdir/tmp should be some directory which is only readable by you. The session data will be stored in that directory.
  • "associate => $cgi" makes all CGI parameters available for the template. To avoid potential security problems the "print $template2->output()" statement should be within an if-else statement, which checks security.
  • Instead of using "associate => $cgi", it is also possible to send the parameters from the cgi object to the session to the template individually with:
    $session->param("name", $cgi->param("name"));
    $template2->param(name => $session->param("name"));
    But before printing the session parameters directly, they should be save into other variables.
  • "$session->save_param($cgi);" saves all CGI parameters into the session.
  • If a user disables cookies, the session ID can be transmitted as a parameter CGISESSID.
  • More information about CGI::Session can be found here

    For the second CGI file, you'll need something like

    my $cgi = new CGI;
    my $session = new CGI::Session(undef, $cgi, {Directory=>'/home/yourdir/tmp'});
    print $cgi->header();
    my $template = HTML::Template->new(scalarref =>\$template_text, associate => $session);

    After those lines, all CGI parameters are available within the template. Note, that in this case the template is associated with the session object, not with the cgi object.

    1.5 Hit Counter - Exercise

    4) Create a file that contains only the number "0". Your CGI script must open that file for reading; read the first line of the file into a scalar variable; increase the number by one; close the file; open the file again for writing (not appending); write the number to the file; close the file.

    2 Optional: Password protected pages

    Access to web sites on an Apache Server can be controlled using the HTACCESS files. Two files are involved in access control: a .www-password file is created and saved in a directory above the public_html directory. Any directory under the public_html directory can then be protected by saving a .htaccess file in the directory. It should be noted that htaccess only encrypts the password but not the pages. Therefore it does not provide high level security. Furthermore, htaccess does not protect your files from being viewed by other Unix users on the same computer.

    2.1 Optional exercise

    Create a password protected directory under your public_html directory. Specific instructions:
  • Go to your top level directory and type htpasswd ~/.www-password remoteuser at the Unix prompt. You can replace remoteuser with a login name you want to use for your pages. Type in a password when prompted. Note that this should be a new password, different from any password you are using for other purposes. The .www-password file must be readable by others.
  • Create a new directory under public_html. Go to the directory. Create a file called .htaccess with the following content:
    AuthType Basic
    AuthName "Password Required"
    AuthUserFile /home/yourmatricnumber/.www-password
    AuthGroupFile /dev/null
    <Limit GET POST>
    require user remoteuser
    </Limit>
    where yourmatricnumber is your matric number and remoteuser is the same name you used above.
  • Change the .htaccess file to readable (chmod 644 .htaccess)
  • If you view any html from that directory through your browser, you should now be prompted for a username and password.

    3 Optional: Abbreviations for writing HTML tags

    The CGI.pm module provides standard subroutines for printing certain HTML code fragments. For example, print h1("Tea"); prints an h1 header. To use these subroutines, the script must contain the line "use CGI qw(:standard);".

  • print header();
  • print start_html(-title=>"title_goes_here",-BGCOLOR=>'yellow');
  • print h1("some heading");
  • print hr();
  • print p("text");
  • print start_form();
  • print a({href=>"url_goes_here.html"},"link text goes here");
  • print textfield(-name=>'field_name',-default=>'starting value', -size=>50,-maxlength=>80);
  • print textarea(-name=>'foo',-default=>'starting value',-rows=>10, -columns=>50);
  • print password_field(-name=>'secret',-value=>'starting value', -size=>50, -maxlength=>80);
  • print popup_menu(-name=>'menu_name',-values=>['one','two','three'], -default=>'two');
  • print scrolling_list(-name=>'list_name',-values=>['one','two','three'], -default=>['one','three'];-size=>3, -multiple=>'true', -labels=>\%labels);
  • print checkbox_group(-name=>'group_name',-values=>['one','two','three'], -default=>['one','three'],-linebreak=>'true',-labels=>\%labels);
  • print checkbox(-name=>'checkbox_name', -checked=>'checked', -value=>'TURNED ON', -label=>'Turn me on');
  • print radio_group(-name=>'group_name',-values=>['one','two','three'], -default=>'one', -linebreak=>'true', -labels=>\%labels);
  • print submit(-name=>'button_name', -value=>'value');
  • print hidden(-name=>'hidden_name', -default=>['value1','value2']);
  • print reset();
  • print end_form();