This site is now 100% read-only, and retired.

Using one time passwords to temporarily open firewall ports

Posted by hamal on Tue 4 Dec 2007 at 12:48

I use Xen to create multiple locked down virtual machines that to run services which I want to present to the internet I do not allow direct connections from the internet to my firewall but sometimes there's a need to do remote administration (via ssh) so I can temporarily open up one or more ports. This I do with a webpage where an OPIE (one time password) challenge should be entered.

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:
Sesame flow

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;
}


The template file is very straight-forward:
<!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>


On the firewall end we have inetd listen to the specified port (here: port 54321) and send any incoming string to the script to add firewall rules. Because of the latter task this script needs to run as root. You can specify multiple tcp and/or udp ports in this script and for each of these, a firewall rule will be added to allow packets for that rule from the specified IP address, then the script will sleep for some time (default 5 minutes) and then all added rules are deleted again. Existing sessions stay active because of the stateful checks of iptables. The script:

#!/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. If you don't have a device with you that can run an OTP calculator, you can pre-calculate a number of responses with the opiekey command (using the -n option) and print them out to keep in your wallet.

 

 


more opie clients
Posted by undefined (192.91.xx.xx) on Tue 4 Dec 2007 at 19:51
to add to your list of opie clients:
- 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 ]

Re: more opie clients
Posted by hamal (80.126.xx.xx) on Tue 4 Dec 2007 at 20:59
Thanks for the extra links. Very good to know

Regards,
Rob

[ Parent ]

Re: Using one time passwords to temporarily open firewall ports
Posted by Anonymous (82.135.xx.xx) on Wed 5 Dec 2007 at 14:23
Also another good software for j2me mobiles:
http://www.fatsquirrel.org/software/vejotp/

[ Parent ]

Re: Using one time passwords to temporarily open firewall ports
Posted by Anonymous (81.169.xx.xx) on Tue 5 Feb 2008 at 14:47
I am triing to implementate this in an endian firewall, which is a debian based FW.

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 ]

Re: Using one time passwords to temporarily open firewall ports
Posted by hamal (145.117.xx.xx) on Fri 8 Feb 2008 at 12:08
Hi Pascal,

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 ]

Re: Using one time passwords to temporarily open firewall ports
Posted by Anonymous (80.95.xx.xx) on Wed 7 Jan 2009 at 16:53
how about string match in iptables periodically updated by the current one-time password and netcat any tcp port. just typing in the browser http://site:port/password
btw. to not ruin the security this requires the payload encryption with decription right beore the iptables match.

[ Parent ]

Re: Using one time passwords to temporarily open firewall ports
Posted by Anonymous (189.165.xx.xx) on Fri 29 Jan 2016 at 18:00
This seems overly complicated and could easily be simplified....
I suggest that one use a hidden URL or page were a visit and perhaps a simple (fixed or dynamic) URL variable be used. In this way you only need to visit the hidden URL to open the firewall to the visiting IP address. Who is going to know the hidden page is there?

This is what I want to implement and I came across this page here .

[ Parent ]