#!/usr/local/bin/perl ################################################################ # # $Id: knockclient,v 1.13 2003/03/03 07:32:11 martink Exp $ # # Copyright 2003 Martin Krzywinski (martink@bcgsc.ca) # # This file is part of a Perl port knocking implementation. # # This port knocking implementation is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This port knocking implementation is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Clusterpunch; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ################################################################ ################################################################ # # http://mkweb.bcgsc.ca/portknocking # # WARNING # This implementation is a PROTOTYPE and will always # be a PROTOTYPE. The knockclient/knockdaemon pair of scripts # is designed to illustrate the port knocking method. Don't use # these scripts in a production environment. Don't expect a lot # of functionality. If you want to write your own implementation, # let me know and I'll post it on the site. # # OK # Now you can read on ;) # ################################################################ ################################################################ # # sample port knocking client which creates encrypted knocks of the form # IP1 IP2 IP3 IP4 PORT TIME CHECKSUM # # knockclient -c IPCLIENT -r IPREMOTE -p PORT -t TIMEVALUE [-help] [-man] # # Open port 22 on IPREMOTE to IPCLIENT # # e.g. knockclient -c IPCLIENT -r IPREMOTE -p 22 -t 0 # # Close port 22 on IPREMOTE to IPCLIENT # # e.g. knockclient -c IPCLIENT -r IPREMOTE -p 22 -t 255 # # Open port 22 on IPREMOTE to IPCLIENT for 15 minutes then # close the port automatically # # e.g. knockclient -c IPCLIENT -r IPREMOTE -p 22 -t 15 # ################################################################ use strict; use Math::VecStat qw(sum); use Crypt::CBC; use IO::Socket::INET; use Pod::Usage; use Getopt::Long; use Tk; use vars qw($opt_client $opt_remote $opt_port $opt_time $opt_help $opt_man $opt_server); my $pod_usage_message = "PORT KNOCKING Prototype Perl Implementation\n\nknockclient - client for sending knocks to remote host.\n"; GetOptions("client=s","remote=s","port=i","time=i","help","man") || pod2usage({-message=>$pod_usage_message}); pod2usage({-message=>$pod_usage_message}) if ($opt_help); pod2usage(-verbose => 2) if ($opt_man); ##### My code my $knock_gui = MainWindow->new; $knock_gui->title("Port Knocker"); $knock_gui->Label(-text => "Client IP") ->grid(-row => 0, -column => 0); $knock_gui->Label(-text => "Server IP") ->grid(-row => 1, -column => 0); $knock_gui->Label(-text => "Port") ->grid(-row => 2, -column => 0); $knock_gui->Label(-text => "Time") ->grid(-row => 3, -column => 0); my $text0 = $knock_gui->Entry(-textvariable => \$opt_client) ->grid(-row => 0, -column => 1); my $text1 = $knock_gui->Entry(-textvariable => \$opt_remote) ->grid(-row => 1, -column => 1); my $text2 = $knock_gui->Entry(-textvariable => \$opt_port) ->grid(-row => 2, -column => 1); my $text3 = $knock_gui->Entry(-textvariable => \$opt_time) ->grid(-row => 3, -column => 1); $opt_client || $text0->insert('end', "0.0.0.0"); $opt_server || $text1->insert('end', "0.0.0.0"); $opt_port || $text2->insert('end', "22"); $opt_time || $text3->insert('end', "15"); $knock_gui->Button(-text => "Knock Knock", -command => \&knock)->grid(-row => 4, -column => 0); $knock_gui->Button(-text => "Exit", -command => sub {exit})->grid(-row => 4, -column => 1); MainLoop; sub knock() { $opt_client || die "specify IP address (-client) for which the port will be opened."; # the client host IP $opt_remote || die "specify IP address (-remote) of server listening to knocks"; # the IP of the firewall $opt_port || die "specify remote port (-port)"; # port to manipulate $opt_time = (defined $opt_time)?$opt_time:5; # time flag use constant PORTMIN => 745; use constant KEY => "knock"; # keyphrase for the encryption use constant CIPHER => "Blowfish"; # encryption algorithm my $cipher = Crypt::CBC->new({key => KEY, iv => "01234567", prepend_iv => 0, cipher => CIPHER}); # data to encrypt will be our IP address + port + time + checksum my @data = (split(/\./,$opt_client),$opt_port,$opt_time); # compute crc and push it onto the data array push(@data,sum(@data) % 255); print "encrypting data ",join(" ",@data),"\n"; # encrypt the packed data my $ciphertext = $cipher->encrypt(pack("C*",@data)); # unpack the ciphered data into unsigned char values 0-255 my @cipherpack = unpack("C*",$ciphertext); # create the knock sequence by mapping to ports PORTMIN-PORTMIN+255 print "ciphered data ",join(" ",@cipherpack),"\n"; my @knocks = map {PORTMIN+$_} @cipherpack; print "knock sequence [",int(@cipherpack)," knocks]: ",join(" ",@knocks),"\n"; # create TCP connection to the remote host to each of the ports in the knock sequence for my $port (@knocks) { my $sock = IO::Socket::INET->new(PeerAddr => $opt_remote, PeerPort => $port, Timeout => 0.5, Proto => 'tcp'); print " knocked on $opt_remote:$port\n"; } } exit 0; ################################################################ # Test decrypting the port knock here. ################################################################ my @knocks; my $ciphertext_rev = pack("C*",map {$_ - PORTMIN} @knocks); my $cipher_decrypt = Crypt::CBC->new({key => KEY, iv=>"01234567", prepend_iv => 0, cipher => CIPHER}); my @cleardata = unpack("C*",$cipher_decrypt->decrypt($ciphertext_rev)); print "decrypted: ",join(" ",@cleardata),"\n"; print "crc ok\n" if (sum(@cleardata[0 .. @cleardata-2]) % 255) == $cleardata[-1]; =pod =head1 NAME knockclient - port knocking client responsible for sending knocks to remote firewall where a F is listening =head1 SYNOPSIS > knockclient -c 10.1.1.1 -r 10.0.0.1 -p 22 -t 0 Knock on remote B<-r> IP 10.0.0.1 asking that port B<-p> 22 be opened B<-t 0> to IP 10.1.1.1 > knockclient -c 10.1.1.1 -r 10.0.0.1 -p 22 -t 255 Knock on remote B<-r> IP 10.0.0.1 asking that port B<-p)>22 be closed B<-t 255> to IP 10.1.1.1 > knockclient -c 10.1.1.1 -r 10.0.0.1 -p 22 -t 15 Knock on remote B<-r> IP 10.0.0.1 asking that port B<-p> 22 be opened to IP 10.1.1.1 and closed automatically after 15 minutes B<-t 15>. > knockclient -h View usage help. > knockclient -man View full manpage. =head1 OPTIONS =over =item Parameters Longer command line parameters are supported. > knockclient -client 10.1.1.1 -remote 10.0.0.1 -port 22 -time 0 =item Cipher By default, the knock sequence will be encrypted using a Blowfish cipher. To change the cipher alter the line use constant CIPHER => "Blowfish"; in both the knockclient and knockdaemon. =item Passphrase The passphrase "knock" is used by default to encrypt the knock sequence. To change the passphrase alter the line use constant KEY => "knock"; in both the knockclient and knockdaemon. =item Port Mapping The encrypted knock values (0-255) are mapped onto ports 745-1000. To change the lower range alter the line use constant PORTMIN => 745; in both the knockclient and knockdaemon. =item Initialization Vector No initialization vector is used in the cipher. This results in the same encrypted knock sequence for the same input, but shortens the encrypted knock sequence. Encrypting 7 unsigned chars (4 IP bytes, port byte, time byte and checksum byte) results in an encrypted knock sequence of 8 unsigned chars. =back =head1 DESCRIPTION The F is the client program and is part of the B Perl prototype. The server side script is F. Refer to L for details about this script. The F is reponsible for encoding command line parameters (client IP address, port, time flag), encrypting them with the cipher of choice (default Blowfish), mapping them onto a set of ports and attempting TCP connections to B at the remote IP address. The connection attempts will fail but will trigger the F firewall log file monitor to open the port. =head2 Example For example if port ssh/22 (B<-p 22>) were to be opened for 15 minutes (B<-t 15>) to client 142.103.205.1 (B<-c 142.103.205.1>) on firewall at IPREMOTE the command would be knockclient -r IPREMOTE -c 142.103.205.1 -p 22 -t 15 The the list of values used to generate the knock sequence would be 142 103 205 1 22 15 233 where the last value is a checksum (mod 255) of the first 6 values. Once this list is encrypted and expressed in unsigned chars it becomes 221 169 50 219 86 117 62 187 The list is finally mapped onto a list of ports PORTMIN-PORTMIN+255 (default PORTMIN=745) and the full port knock sequence becomes 966 914 795 964 831 862 807 932 These are the ports to which the client will attempt to connect. On the host at IPREMOTE the daemon F must be monitoring the firewall log file. Once the sequence is detected a rule will be inserted into the firewall to open port 22 to the client address 142.103.205.1. =head1 AUTHOR Martin Krzywinski, martink@bcgsc.ca =head1 SEE ALSO F =head1 HISTORY =over =item 25 February 2002 Initial Perl prototype =back =head1 COPYRIGHT (c) 2003 Martin Krzywinski All rights reserved. This port knocking implementation is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Refer to COPYING in this distribution for the complete GPL license =cut