|
|
- #!/usr/bin/perl
- #
- # knockgen 0.1 - Random port & protocol sequence generator for knockd daemon
- # Copyright (C) 2018 Pekka Helenius
- #
- # This program 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 3 of the License, or
- # (at your option) any later version.
- #
- # This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
- #
- #-----------------------------------------
- # PROGRAM DESCRIPTION
-
- # This perl program generates a random port & protocol sequence for knockd daemon.
- # This generated sequence can be used to knock ports of the target machine (usually a SSH server behind a firewall).
- # The generated data must be equal for the server (knockd daemon computer) and your client (ssh/knock client computer).
-
- #-----------------------------------------
- # ENVIRONMENT
-
- use strict;
- use warnings;
-
- # For CLI input parameters
- use Getopt::Long;
-
- # For help text
- use Pod::Usage;
-
- # For IP regular expressions
- # Requires 'perl-regexp-common' package (e.g. Arch Linux package database)
- use Regexp::Common qw/net number/;
-
- # Requires 'perl-io-socket-inet6' package (e.g. Arch Linux package database)
- use IO::Socket;
-
- # For pinging target hosts
- use Net::Ping;
-
-
- # TODO output file (Default: /etc/knockd.conf)
- # Check if the file exists
- # Do same checks than in the bash script
-
- # Ask user for these
- # TODO knockd daemon: Configuration file? [default: /etc/knockd.conf ]
- # TODO knockd daemon: Network interface for daemon? [default: eth0 ]
- # TODO knockd daemon: Time limit for port knocking in seconds? [default: 10 ]
- # TODO knockd daemon: Ports to be opened after knocking? [default: 22 ]
- # TODO knockd daemon: Open port for specified IP or any client? [default: any ]
- # TODO knockd daemon: How long to keep the port opened in seconds? [default: 15 ]
- # TODO knockd daemon: TCP Flags? [default: syn ]
- # TODO knockd daemon: Use log file /var/log/knockd.log? [default: n]
-
- # TODO If previous knockd configuration detected (get creation date of it), warn sysadmin about it
- # TODO add commented date tag to generated knockd.conf file (for sysadmins)
- # TODO Ask user if the generated port sequence pattern is ok or generate a new one
- # TODO if output/override etc parameter is given with valid input, use it instead of generating a new one
-
- # TODO detect old SSH configurations from /etc/iptables/*.rules files. Warn user about them and delete if permission granted
- # Do this by detecting the the ports which should be opened by knockd
-
- # TODO support for multiple port openings. Can we do this just by adding a new port to IPTABLES rule or do we have to generate a new
- # rule for each port?
-
- #-----------------------------------------
- # DEFAULT VALUES
- #
- # Port test before generating values for knockd.
- # We need testing because we want to avoid any ports which may be listened/used by a running server daemon.
- #
- my $default_target_ip = "127.0.0.1"; # IPv4 address of the target host computer. Default: 127.0.0.1
- my $connection_timeout = 0.3; # Connection timeout in seconds. Default: 0.3
- my $connection_timeout_minlimit = 0.2; # This is the minimum time out limit for connection attempts
- my $connection_timeout_maxlimit = 5.0; # This is the maximum time out limit for connection attempts
-
- # Knockd specific values
- my $knockd_protocols = "tcp,udp"; # Protocols to be used. Only tcp or udp is accepted.
- my $knockd_port_count = 6; # How many port and protocol combinations we generate for knockd input.
- my $knockd_port_count_limit = 30; # This is the maximum amount of ports accepted to output.
- my ($knockd_min_port, $knockd_max_port) = (1, 65535); # Scanned port range. Default: 1, 65535
-
- # TODO override port pattern
- # TODO override port + protocol pattern
-
- # TODO parameter: output pattern only, no knockd questions described above
-
- # TODO knockd_protocols: check for input values (must be either tcp or udp, max values (array length) is 2"
- # TODO knockd_port_count: set minimum limit to 2
-
- # TODO check for iptables Default policy, too (does it reject all traffic etc)
- #/usr/bin/iptables -I INPUT -p tcp -dport 5461 -j ACCEPT
- #/usr/bin/iptables -D INPUT -p tcp -dport 5461 -j ACCEPT
-
- #-----------------------------------------
- # USER INPUT PARAMETERS
-
- my %opts = ("all" => 0,
- "ports-only" => 0,
- "target-ip" => $default_target_ip,
- "dry-gen" => 0,
- "random-always" => 0);
-
- GetOptions ("a|all" => \$opts{"all"},
- "p|ports-only" => \$opts{"ports-only"},
- "i|target-ip=s" => \$opts{"target-ip"},
- "d|dry-gen" => \$opts{"dry-gen"},
- "r|random-always" => \$opts{"random-always"},
- "t|conn-timeout=f" => \$connection_timeout,
- "c|protocols=s" => \$knockd_protocols,
- "m|min-port=i" => \$knockd_min_port,
- "x|max-port=i" => \$knockd_max_port,
- "n|port-count=i" => \$knockd_port_count,
- "h|help" => sub { pod2usage(1) })
- or pod2usage(2);
-
- my $all = $opts{"all"};
- my $ports_only = $opts{"ports-only"};
- my $target_ip = $opts{"target-ip"};
- my $dry_gen = $opts{"dry-gen"};
- my $random_always = $opts{"random-always"};
-
- #-----------------------------------------
- # ERROR HANDLING
-
- # We don't accept regular PERL arguments here. We take arguments only from GetOptions
- # Perl arguments are handled differently, we don't want them.
- if (@ARGV)
- {
- print "Unknown option: @ARGV\n";
- pod2usage(2);
- }
-
- #--------------------
- # If 'target_ip' is default value and 'dry-gen' is set, clear the IP value.
- if ($target_ip eq $default_target_ip and $dry_gen) {
- print "WARNING: Not testing whether generated ports are being used or not.\n";
- undef $target_ip;
- }
-
- #--------------------
- # If both 'dry-gen' and 'target_ip' is set, terminate.
- if ($target_ip and $dry_gen) {
- die "ERROR: Define either target IPv4 address or 'dry-gen' parameter.\n";
- }
-
- #--------------------
- # If neither 'ports-only' or 'all' hash is set, use default 'all' value.
- if (not $ports_only and not $all) {
- $all = 1;
- }
-
- #--------------------
- # If both 'ports-only' and 'all' is set, terminate.
- if ($ports_only and $all) {
- die "ERROR: Define either 'ports-only' or 'all' (default is 'all').\n";
- }
-
- #--------------------
- # If more than allowed ports have been instructed.
- if ($knockd_port_count > $knockd_port_count_limit) {
- die "ERROR: Maximum number of ports is " . $knockd_port_count_limit . ". You have defined " . $knockd_port_count . " ports.\n"
- }
-
- #--------------------
- # Check for invalid port values (min & max)
- my $port_fault = "";
- if (($knockd_min_port < 1) or ($knockd_min_port > 65535))
- {
- print "ERROR: Minimum port value is not in valid range 1-65535 (Value: " . $knockd_min_port . ")\n";
- $port_fault = "true";
- }
-
- if (($knockd_max_port < 1) or ($knockd_max_port > 65535))
- {
- print "ERROR: Maximum port value is not in valid range 1-65535 (Value: " . $knockd_max_port . ")\n";
- $port_fault = "true";
- }
-
- # If faulty port values, exit the program
- # We define the exit procedure separately because we want to print all previous port-related error messages
- if ( $port_fault eq "true" ) { exit }
-
- #--------------------
- # Inform user that the minimum port value exceeds maximum port value.
- if ($knockd_min_port > $knockd_max_port)
- {
- die "ERROR: Minimum port value is set above maximum port value.\n";
- }
-
- #--------------------
- # Inform user that more random ports must be available in the given pool.
- if (($knockd_max_port - $knockd_min_port) < $knockd_port_count)
- {
- die "ERROR: You must have more ports for randomizable port sequence (Current port pool size: " . ($knockd_max_port - $knockd_min_port) . ")\n";
- }
-
- #--------------------
- # Minimum connection time out value can't be less than the limit value defines.
- if ($connection_timeout < $connection_timeout_minlimit)
- {
- die "ERROR: Connection time out can't be set below " . $connection_timeout_minlimit . " seconds.\n";
- }
-
- # Maximum connection time out value can't exceed the maximum limit value.
- if ($connection_timeout > $connection_timeout_maxlimit)
- {
- print "WARNING: Connection time out limit is set above recommended limit (" . $connection_timeout_maxlimit . " seconds).\n";
- }
-
- #--------------------
- # If user is not having dry-gen parameter set...
- if (not $dry_gen) {
-
- # ...check for valid IP address syntax.
- if (not $target_ip =~ /^$RE{net}{IPv4}$/ or $target_ip eq "localhost") {
- die "ERROR: Invalid IPv4 address given (" . $target_ip . ")\n"
- }
-
- # ... check that we can establish a connection to the target IP address.
- my $p = Net::Ping->new;
- if (not $p->ping($target_ip, $connection_timeout_maxlimit)) {
- die "ERROR: Can't find the host computer.\n"
- }
- }
-
- #-----------------------------------------
- # PORT & PROTOCOL GENERATION
-
- # Split user input into a new array, defined by @ symbol.
- my @knockd_protocols = split /,/, $knockd_protocols;
-
- # Set port range.
- my @knockd_port_range = ($knockd_min_port .. $knockd_max_port);
-
- # Declare a new array for generated ports (+ protocols).
- # Fill with zeros, size is value of $knockd_port_seqs.
- my @randoms = (0) x $knockd_port_count;
-
- my $i = 0;
- while ($knockd_port_count > 0)
- {
-
- while ()
- {
-
- # Generate a random port from given port range.
- my $random_port = $knockd_port_range[rand @knockd_port_range];
-
- # Choose randomly between protocols tcp & udp.
- my $random_protocol = $knockd_protocols[rand @knockd_protocols];
-
- # TODO make this optional with '$always_random' user input parameter
- # Never accept already generated port in port sequence.
- if (not grep {$_ =~ /$random_port/} @randoms)
- {
-
- # Initialize socket value
- my $socket = 0;
-
- if (not $dry_gen) {
-
- # Test the generated port for a connection.
- # Protocol must always be else than udp because udp doesn't give
- # a response whether the connection was successful or not.
- # This is in the procotol specification.
- #
- # Therefore, udp would fail the connection test we establish here
- #
- $socket = IO::Socket::INET->new(PeerHost => $target_ip,
- PeerPort => $random_port,
- Proto => "tcp", #This is hardcoded purposefully
- Timeout => $connection_timeout);
-
- }
- else
- {
- undef $socket;
- }
-
- # If socket dest is not used
- if (not $socket)
- {
- if ( $all ) {
- $randoms[$i] = $random_port . ":" . $random_protocol;
- }
- else
- {
- $randoms[$i] = $random_port;
- }
- last;
- }
- }
- }
- $knockd_port_count--;
- $i++;
- }
-
- my $knockd_seqs = join(',', @randoms);
- print $knockd_seqs . "\n";
-
- #-----------------------------------------
- # HELP TEXT
-
- __END__
-
- # TODO IMPROVE THIS SECTION
- # TODO UPDATE HELP TEXT TO CORRESPOND WITH THE COMMANDS ABOVE
-
- =head1 SYNOPSIS
-
- knockgen [options] (--help or -h for more information)
-
- knockgen 0.1 - Random port & protocol sequence generator for knockd daemon
-
-
- knockgen Copyright (C) 2018 Pekka Helenius <fincer89@hotmail.com>
- This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-
- =head1 OPTIONS
-
- =item A<------------------------------>
-
- =item A<Output format:>
-
- =item B<--all> or B<-a>
-
- Print randomly generated ports & protocols (Default)
-
- =item B<--ports-only> or B<-p>
-
- Print randomly generated ports only
-
- =item B<--dry-gen> or B<-d>
-
- Generate port (& protocol) sequence without trying to connect anywhere.
-
- =item A<------------------------------>
-
- =item A<Port testing:>
-
- =item B<--target-pc> or B<-i>
-
- IPv4 address of the target computer (server). Default: 127.0.0.1
-
- =item B<--protocols> or B<-c>
-
- Use these protocols. Default: tcp,udp (Syntax: tcp or tcp,udp)
-
- =item B<--conn-timeout> or B<-t>
-
- Connection timeout for a port test in seconds. Default: 0.3
-
- =item A<------------------------------>
-
- =item A<Generated values:>
-
- =item B<--min-port> or B<-m>
-
- Minimum port number to be used for knockd daemon. Default: 1
-
- =item B<--max-port> or B<-x>
-
- Maximum port number to be used for knockd daemon. Default: 65535
-
- =item B<--port-seqs> or B<-n>
-
- Number of port (+ protocol) sequences for knockd daemon. Default: 6
-
- =item B<--help> or B<-h>
-
- Prints this help text.
-
- =cut
|