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();