Using one time passwords to temporarily open firewall ports
Posted by hamal on Tue 4 Dec 2007 at 12:48
The idea of using OPIE challenges to open up firewall ports is not new. I saw
this option with Checkpoint FW1 firewalls. I implemented this myself with a
couple of perl scripts. The system is presented in the following image:

I connect to a CGI page which presents me with an OPIE challenge and a box to
fill in an IP address. The latter will default to the connecting IP address,
but the option is provided for when your ISP uses a transparent proxy or your
browser is configured to use a web proxy. If the verification succeeds, the IP
address is sent to a dedicated tcp port on the firewall. Behind that port
another script waits, parses the IP address and adds one or more rules for the
INPUT table to the iptables rulebase. The ports that are added to the rulebase
are hardcoded in the listening script on the firewall. The script will then
sleep for a limited time (I use 5 minutes) and then remove all added rules
from the firewall rulebase. Sessions that have been established during that
time can be kept alive by using state checking in your iptables rulebase.
Implementation
The webserver needs support for OPIE verification. That is provided with the opie-server tools which is standard in debian etch. Since this verification has to be done via a CGI script, I created a dedicated non-privileged user, changed the ownership of /etc/opiekeys to that user and run the script as that user via the suexec mechanism of Apache. The script uses the HTML::Template and Authen::OPIE perl modules. The latter is not available as a package in debian etch, so you should create it yourself with dh_perl (part of the debhelper package). To create the package, you need to have the libopie-dev package installed. Alternately, you can fetch the package from my site. This is the CGI script:
#!/usr/bin/perl -w
# vim:ai:filetype=perl:sta:sw=4:et:
#
# This CGI script will use the S/Key One-Time
# Password mechanism for verification and if
# sucessfull, will pass an IP address (default:
# $ENV{REMOTE_ADDR}) to a tcp socket for inclusion
# in a firewall rulebase
#
use strict;
use CGI qw /:standard/;
use CGI::Carp 'fatalsToBrowser';
use HTML::Template;
use IO::Socket;
use Authen::OPIE qw(opie_challenge opie_verify);
# var declarations
our ($cgi, $template);
our $opie_user="opie";
our $fwhost="192.168.1.1";
our $fwport="54321";
$cgi=CGI->new();
print $cgi->header();
if (not defined $cgi->param(-name=>"login") or
$cgi->param(-name=>"login") ne "Open Sesame") {
#
# print challenge screen
#
my $opiechalstr=&opie_challenge($opie_user);
&Barf2Browser("Unknown OPIE user: $opie_user")
if (not defined $opiechalstr);
my @opiechalarr=split(/ /, $opiechalstr);
if ($opiechalarr[0] ne "otp-md5") {
&Barf2Browser("Crazy challenge: $opiechalstr");
}
my $response=$cgi->textfield(-name=>"response",
-value=>"",
-size=>40);
my $ipaddr=$cgi->textfield(-name=>"ipaddr",
-value=>"",
-size=>16);
my $submitbutton=$cgi->submit(-name=>"login",
-value=>"Open Sesame");
$template=HTML::Template->new(filename =>"sesame.tmpl",
path => "/home/$opie_user/templates");
$template->param(chalbool => 1);
$template->param(formstart => $cgi->start_form);
$template->param(sequence => $opiechalarr[1]);
$template->param(seed => $opiechalarr[2]);
$template->param(response => $response);
$template->param(ipaddr => $ipaddr);
$template->param(curraddr => $ENV{REMOTE_ADDR});
$template->param(submit => $submitbutton);
print $template->output;
}
else {
#
# Verify the challenge
#
my $response=$cgi->param(-name=>"response");
my $ipaddr=$cgi->param(-name=>"ipaddr");
&Barf2Browser("Empty response")
if (not defined $response or $response eq "");
my $verifyval=&opie_verify($opie_user,$response);
if (not defined $verifyval or $verifyval != 0) {
&Barf2Browser("<span class=red>Athentication attempt FAILED</span>");
}
else {
#
# OTP challenge succeeded, send IP address to firewall
#
$ipaddr=$ENV{REMOTE_ADDR} if (not defined $ipaddr or $ipaddr eq "");
my $socket= new IO::Socket::INET (PeerAddr => $fwhost,
PeerPort => $fwport,
Proto => "tcp",
Type => SOCK_STREAM)
or &Barf2Browser("Authentication succeeded but "
."<span class=red>network connection failed</span>");
print $socket "$ipaddr";
close $socket;
my $submitbutton=$cgi->submit(-name=>"ok",
-value=>"OK");
$template=HTML::Template->new(filename =>"sesame.tmpl",
path => "/home/$opie_user/templates");
$template->param(msgbool => 1);
$template->param(formstart => $cgi->start_form);
$template->param(msghdr => "<span class=blue>Authentication succeeded!</span>");
$template->param(message => "Sent $ipaddr to the firewall");
$template->param(submit => $submitbutton);
print $template->output;
}
}
exit 0;
sub Barf2Browser() {
# output error
my ($string)=@_;
my $submitbutton=$cgi->submit(-name=>"ok",
-value=>"OK");
$string="undefined" if (not defined $string);
$template=HTML::Template->new(filename =>"sesame.tmpl",
path => "/home/$opie_user/templates");
$template->param(msgbool => 1);
$template->param(formstart => $cgi->start_form);
$template->param(msghdr => "Sesame error:");
$template->param(message => $string);
$template->param(submit => $submitbutton);
print $template->output;
exit 0;
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<style type="text/css">
BODY {background-color: #b0c4ef; color: black}
A:link {color: #000040}
A:external {color: #000040}
A:active {color: #000040}
A:visited {color: #000040}
SPAN.blue {color: #0000c0}
SPAN.red {color: #c00000}
</style>
</head>
<body>
<tmpl_var name="formstart">
<h1>Sesame Verifyer</h1>
<tmpl_if name="chalbool">
<h3>Challenge:<br/>
<tmpl_var name="sequence"> <tmpl_var name="seed"></h3>
<br/>
<strong>Response:</strong><br/>
<tmpl_var name="response"><br/><br/>
<table><tr><td>
Current IP address:
</td><td>
<tt><strong><tmpl_var name="curraddr"></strong></tt>
</td></tr><tr><td>
Optionally override with:
</td><td>
<tmpl_var name="ipaddr">
</td></tr></table>
<tmpl_else>
<tmpl_if name="msgbool">
<h3><tmpl_var name=msghdr></h3>
<strong><tmpl_var name="message"></strong>
</tmpl_if>
</tmpl_if>
<br/><br/><tmpl_var name="submit">
</form>
</body>
</html>
#!/usr/bin/perl
# vim:ai:filetype=perl:sta:sw=4:et:
#
# This script will read a line from STDIN, expecting
# expecting an IP address and will use that address
# as a source for a firewall rule that temporarily
# opens one or more ports in the INPUT chain
#
# ports must be specified as /^[tu]\d+$/ (the first
# character specifies the tcp or udp protocol
use strict;
use Sys::Syslog qw(:standard :macros);
our @ports = ("t22","u5000");
our $timeslot = 300; #5 minutes
my $addr=<STDIN>;
chomp $addr;
# check if we received a correct IP address
if ($addr !~ /^(\d{1,3}\.){3}\d{1,3}$/) {
&LogText(LOG_WARNING, "WARNING: someone tried something nasty!");
exit 0;
}
foreach my $port (@ports) {
&IPTrule("-I",$port,$addr);
}
sleep $timeslot;
foreach my $port (@ports) {
&IPTrule("-D",$port,$addr);
}
exit 0;
sub LogText() {
my ($level, $text)=@_;
openlog("sesamed","ndelay,pid",LOG_DAEMON);
syslog($level,$text);
closelog;
}
sub IPTrule() {
my ($act,$protoport,$addr)=@_;
my ($proto,$port)=();
if ($protoport =~ /^([tu])(\d+)$/) {
$port=$2;
$proto = ($1 eq "t") ? "tcp" : "udp";
}
if (not defined $proto) {
&LogText(LOG_NOTICE, "BUG: wrong protoport specified");
return;
}
my @cmdline=("/sbin/iptables",$act,"INPUT","-p",$proto,"--dport");
push @cmdline, ($port,"-s",$addr,"-j","ACCEPT");
&LogText(LOG_INFO, join(" ", @cmdline));
system(@cmdline);
}
Calculating the OPIE challenge
Note: Calculating the response to an OPIE challenge should not be done over an insecure line. That would defeat the whole purpose of using one-time passwords.The OPIE system presents you with a sequence number and a seed. You enter these in your OPIE calculator, enter the password and the one-time password (consisting of 6 english words) is generated. There are various calculators available.
- for debian: the opiekey command
- for MacOS: SkeyCalc
- for Windows: WinKey
- for PocketPC: AWE OTP
- for PalmOS: PalmKey
- otpcalc on nokia 770 (and maybe n800 running os2007)
- otpgen for j2me (haven't used it, just referenced by above otpcalc page)
i use linux-vserver, libpam-opie, ssh, ajaxterm (reverse proxied by apache2) or putty (depending on how restrictive of a firwall i'm behind), and palmkey and that works well.
[ Parent | Reply to this comment ]
Regards,
Rob
[ Parent | Reply to this comment ]
[ Parent | Reply to this comment ]
when I surf to the first cgi script , I have called it /home/httpd/html/auth2/index.cgi i get this error
:
Software error:
Can't locate loadable object for module Authen::OPIE in @INC (@INC contains: /usr/lib/perl5/5.8.5/i386-linux-thread-multi /usr/lib/perl5/5.8.5 /usr/lib/perl5/site_perl/5.8.5/i386-linux-thread-multi /usr/lib/perl5/site_perl/5.8.4/i386-linux-thread-multi /usr/lib/perl5/site_perl/5.8.3/i386-linux-thread-multi /usr/lib/perl5/site_perl/5.8.2/i386-linux-thread-multi /usr/lib/perl5/site_perl/5.8.1/i386-linux-thread-multi /usr/lib/perl5/site_perl/5.8.0/i386-linux-thread-multi /usr/lib/perl5/site_perl/5.8.5 /usr/lib/perl5/site_perl/5.8.4 /usr/lib/perl5/site_perl/5.8.3 /usr/lib/perl5/site_perl/5.8.2 /usr/lib/perl5/site_perl/5.8.1 /usr/lib/perl5/site_perl/5.8.0 /usr/lib/perl5/site_perl /usr/lib/perl5/vendor_perl/5.8.5/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.4/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.3/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.2/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.1/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.0/i386-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.5 /usr/lib/perl5/vendor_perl/5.8.4 /usr/lib/perl5/vendor_perl/5.8.3 /usr/lib/perl5/vendor_perl/5.8.2 /usr/lib/perl5/vendor_perl/5.8.1 /usr/lib/perl5/vendor_perl/5.8.0 /usr/lib/perl5/vendor_perl .) at /home/httpd/html/auth2/index.cgi line 15
Compilation failed in require at /home/httpd/html/auth2/index.cgi line 15.
BEGIN failed--compilation aborted at /home/httpd/html/auth2/index.cgi line 15.
For help, please send mail to the webmaster (root@localhost), giving this error message and the time and date of the error.
can someone help me implementating this script ?
thanks
Pascal
Info@4-strokeproduktunes.com
[ Parent | Reply to this comment ]
You need the Athen::OPIE perl module for this to work. Either install it locally by unpacking the tarball from CPAN, and running "perl Makefile.PL ; make ; make install" or use dh_perl to create a package. If the Endian firewall is i386 based, you could also use my package from here.
--
Hamal is a K2 star in the constellation Aries.
It is 20 pc away so it has no effect on your personality.
[ Parent | Reply to this comment ]
btw. to not ruin the security this requires the payload encryption with decription right beore the iptables match.
[ Parent | Reply to this comment ]