Instructions to set up a basic LAMP+SSH server environment
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

385 lines
13 KiB

  1. #!/usr/bin/perl
  2. #
  3. # knockgen 0.1 - Random port & protocol sequence generator for knockd daemon
  4. # Copyright (C) 2018 Pekka Helenius
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. #
  19. #-----------------------------------------
  20. # PROGRAM DESCRIPTION
  21. # This perl program generates a random port & protocol sequence for knockd daemon.
  22. # This generated sequence can be used to knock ports of the target machine (usually a SSH server behind a firewall).
  23. # The generated data must be equal for the server (knockd daemon computer) and your client (ssh/knock client computer).
  24. #-----------------------------------------
  25. # ENVIRONMENT
  26. use strict;
  27. use warnings;
  28. # For CLI input parameters
  29. use Getopt::Long;
  30. # For help text
  31. use Pod::Usage;
  32. # For IP regular expressions
  33. # Requires 'perl-regexp-common' package (e.g. Arch Linux package database)
  34. use Regexp::Common qw/net number/;
  35. # Requires 'perl-io-socket-inet6' package (e.g. Arch Linux package database)
  36. use IO::Socket;
  37. # For pinging target hosts
  38. use Net::Ping;
  39. # TODO output file (Default: /etc/knockd.conf)
  40. # Check if the file exists
  41. # Do same checks than in the bash script
  42. # Ask user for these
  43. # TODO knockd daemon: Configuration file? [default: /etc/knockd.conf ]
  44. # TODO knockd daemon: Network interface for daemon? [default: eth0 ]
  45. # TODO knockd daemon: Time limit for port knocking in seconds? [default: 10 ]
  46. # TODO knockd daemon: Ports to be opened after knocking? [default: 22 ]
  47. # TODO knockd daemon: Open port for specified IP or any client? [default: any ]
  48. # TODO knockd daemon: How long to keep the port opened in seconds? [default: 15 ]
  49. # TODO knockd daemon: TCP Flags? [default: syn ]
  50. # TODO knockd daemon: Use log file /var/log/knockd.log? [default: n]
  51. # TODO If previous knockd configuration detected (get creation date of it), warn sysadmin about it
  52. # TODO add commented date tag to generated knockd.conf file (for sysadmins)
  53. # TODO Ask user if the generated port sequence pattern is ok or generate a new one
  54. # TODO if output/override etc parameter is given with valid input, use it instead of generating a new one
  55. # TODO detect old SSH configurations from /etc/iptables/*.rules files. Warn user about them and delete if permission granted
  56. # Do this by detecting the the ports which should be opened by knockd
  57. # 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
  58. # rule for each port?
  59. #-----------------------------------------
  60. # DEFAULT VALUES
  61. #
  62. # Port test before generating values for knockd.
  63. # We need testing because we want to avoid any ports which may be listened/used by a running server daemon.
  64. #
  65. my $default_target_ip = "127.0.0.1"; # IPv4 address of the target host computer. Default: 127.0.0.1
  66. my $connection_timeout = 0.3; # Connection timeout in seconds. Default: 0.3
  67. my $connection_timeout_minlimit = 0.2; # This is the minimum time out limit for connection attempts
  68. my $connection_timeout_maxlimit = 5.0; # This is the maximum time out limit for connection attempts
  69. # Knockd specific values
  70. my $knockd_protocols = "tcp,udp"; # Protocols to be used. Only tcp or udp is accepted.
  71. my $knockd_port_count = 6; # How many port and protocol combinations we generate for knockd input.
  72. my $knockd_port_count_limit = 30; # This is the maximum amount of ports accepted to output.
  73. my ($knockd_min_port, $knockd_max_port) = (1, 65535); # Scanned port range. Default: 1, 65535
  74. # TODO override port pattern
  75. # TODO override port + protocol pattern
  76. # TODO parameter: output pattern only, no knockd questions described above
  77. # TODO knockd_protocols: check for input values (must be either tcp or udp, max values (array length) is 2"
  78. # TODO knockd_port_count: set minimum limit to 2
  79. # TODO check for iptables Default policy, too (does it reject all traffic etc)
  80. #/usr/bin/iptables -I INPUT -p tcp -dport 5461 -j ACCEPT
  81. #/usr/bin/iptables -D INPUT -p tcp -dport 5461 -j ACCEPT
  82. #-----------------------------------------
  83. # USER INPUT PARAMETERS
  84. my %opts = ("all" => 0,
  85. "ports-only" => 0,
  86. "target-ip" => $default_target_ip,
  87. "dry-gen" => 0,
  88. "random-always" => 0);
  89. GetOptions ("a|all" => \$opts{"all"},
  90. "p|ports-only" => \$opts{"ports-only"},
  91. "i|target-ip=s" => \$opts{"target-ip"},
  92. "d|dry-gen" => \$opts{"dry-gen"},
  93. "r|random-always" => \$opts{"random-always"},
  94. "t|conn-timeout=f" => \$connection_timeout,
  95. "c|protocols=s" => \$knockd_protocols,
  96. "m|min-port=i" => \$knockd_min_port,
  97. "x|max-port=i" => \$knockd_max_port,
  98. "n|port-count=i" => \$knockd_port_count,
  99. "h|help" => sub { pod2usage(1) })
  100. or pod2usage(2);
  101. my $all = $opts{"all"};
  102. my $ports_only = $opts{"ports-only"};
  103. my $target_ip = $opts{"target-ip"};
  104. my $dry_gen = $opts{"dry-gen"};
  105. my $random_always = $opts{"random-always"};
  106. #-----------------------------------------
  107. # ERROR HANDLING
  108. # We don't accept regular PERL arguments here. We take arguments only from GetOptions
  109. # Perl arguments are handled differently, we don't want them.
  110. if (@ARGV)
  111. {
  112. print "Unknown option: @ARGV\n";
  113. pod2usage(2);
  114. }
  115. #--------------------
  116. # If 'target_ip' is default value and 'dry-gen' is set, clear the IP value.
  117. if ($target_ip eq $default_target_ip and $dry_gen) {
  118. print "WARNING: Not testing whether generated ports are being used or not.\n";
  119. undef $target_ip;
  120. }
  121. #--------------------
  122. # If both 'dry-gen' and 'target_ip' is set, terminate.
  123. if ($target_ip and $dry_gen) {
  124. die "ERROR: Define either target IPv4 address or 'dry-gen' parameter.\n";
  125. }
  126. #--------------------
  127. # If neither 'ports-only' or 'all' hash is set, use default 'all' value.
  128. if (not $ports_only and not $all) {
  129. $all = 1;
  130. }
  131. #--------------------
  132. # If both 'ports-only' and 'all' is set, terminate.
  133. if ($ports_only and $all) {
  134. die "ERROR: Define either 'ports-only' or 'all' (default is 'all').\n";
  135. }
  136. #--------------------
  137. # If more than allowed ports have been instructed.
  138. if ($knockd_port_count > $knockd_port_count_limit) {
  139. die "ERROR: Maximum number of ports is " . $knockd_port_count_limit . ". You have defined " . $knockd_port_count . " ports.\n"
  140. }
  141. #--------------------
  142. # Check for invalid port values (min & max)
  143. my $port_fault = "";
  144. if (($knockd_min_port < 1) or ($knockd_min_port > 65535))
  145. {
  146. print "ERROR: Minimum port value is not in valid range 1-65535 (Value: " . $knockd_min_port . ")\n";
  147. $port_fault = "true";
  148. }
  149. if (($knockd_max_port < 1) or ($knockd_max_port > 65535))
  150. {
  151. print "ERROR: Maximum port value is not in valid range 1-65535 (Value: " . $knockd_max_port . ")\n";
  152. $port_fault = "true";
  153. }
  154. # If faulty port values, exit the program
  155. # We define the exit procedure separately because we want to print all previous port-related error messages
  156. if ( $port_fault eq "true" ) { exit }
  157. #--------------------
  158. # Inform user that the minimum port value exceeds maximum port value.
  159. if ($knockd_min_port > $knockd_max_port)
  160. {
  161. die "ERROR: Minimum port value is set above maximum port value.\n";
  162. }
  163. #--------------------
  164. # Inform user that more random ports must be available in the given pool.
  165. if (($knockd_max_port - $knockd_min_port) < $knockd_port_count)
  166. {
  167. die "ERROR: You must have more ports for randomizable port sequence (Current port pool size: " . ($knockd_max_port - $knockd_min_port) . ")\n";
  168. }
  169. #--------------------
  170. # Minimum connection time out value can't be less than the limit value defines.
  171. if ($connection_timeout < $connection_timeout_minlimit)
  172. {
  173. die "ERROR: Connection time out can't be set below " . $connection_timeout_minlimit . " seconds.\n";
  174. }
  175. # Maximum connection time out value can't exceed the maximum limit value.
  176. if ($connection_timeout > $connection_timeout_maxlimit)
  177. {
  178. print "WARNING: Connection time out limit is set above recommended limit (" . $connection_timeout_maxlimit . " seconds).\n";
  179. }
  180. #--------------------
  181. # If user is not having dry-gen parameter set...
  182. if (not $dry_gen) {
  183. # ...check for valid IP address syntax.
  184. if (not $target_ip =~ /^$RE{net}{IPv4}$/ or $target_ip eq "localhost") {
  185. die "ERROR: Invalid IPv4 address given (" . $target_ip . ")\n"
  186. }
  187. # ... check that we can establish a connection to the target IP address.
  188. my $p = Net::Ping->new;
  189. if (not $p->ping($target_ip, $connection_timeout_maxlimit)) {
  190. die "ERROR: Can't find the host computer.\n"
  191. }
  192. }
  193. #-----------------------------------------
  194. # PORT & PROTOCOL GENERATION
  195. # Split user input into a new array, defined by @ symbol.
  196. my @knockd_protocols = split /,/, $knockd_protocols;
  197. # Set port range.
  198. my @knockd_port_range = ($knockd_min_port .. $knockd_max_port);
  199. # Declare a new array for generated ports (+ protocols).
  200. # Fill with zeros, size is value of $knockd_port_seqs.
  201. my @randoms = (0) x $knockd_port_count;
  202. my $i = 0;
  203. while ($knockd_port_count > 0)
  204. {
  205. while ()
  206. {
  207. # Generate a random port from given port range.
  208. my $random_port = $knockd_port_range[rand @knockd_port_range];
  209. # Choose randomly between protocols tcp & udp.
  210. my $random_protocol = $knockd_protocols[rand @knockd_protocols];
  211. # TODO make this optional with '$always_random' user input parameter
  212. # Never accept already generated port in port sequence.
  213. if (not grep {$_ =~ /$random_port/} @randoms)
  214. {
  215. # Initialize socket value
  216. my $socket = 0;
  217. if (not $dry_gen) {
  218. # Test the generated port for a connection.
  219. # Protocol must always be else than udp because udp doesn't give
  220. # a response whether the connection was successful or not.
  221. # This is in the procotol specification.
  222. #
  223. # Therefore, udp would fail the connection test we establish here
  224. #
  225. $socket = IO::Socket::INET->new(PeerHost => $target_ip,
  226. PeerPort => $random_port,
  227. Proto => "tcp", #This is hardcoded purposefully
  228. Timeout => $connection_timeout);
  229. }
  230. else
  231. {
  232. undef $socket;
  233. }
  234. # If socket dest is not used
  235. if (not $socket)
  236. {
  237. if ( $all ) {
  238. $randoms[$i] = $random_port . ":" . $random_protocol;
  239. }
  240. else
  241. {
  242. $randoms[$i] = $random_port;
  243. }
  244. last;
  245. }
  246. }
  247. }
  248. $knockd_port_count--;
  249. $i++;
  250. }
  251. my $knockd_seqs = join(',', @randoms);
  252. print $knockd_seqs . "\n";
  253. #-----------------------------------------
  254. # HELP TEXT
  255. __END__
  256. # TODO IMPROVE THIS SECTION
  257. # TODO UPDATE HELP TEXT TO CORRESPOND WITH THE COMMANDS ABOVE
  258. =head1 SYNOPSIS
  259. knockgen [options] (--help or -h for more information)
  260. knockgen 0.1 - Random port & protocol sequence generator for knockd daemon
  261. knockgen Copyright (C) 2018 Pekka Helenius <fincer89@hotmail.com>
  262. This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
  263. This is free software, and you are welcome to redistribute it
  264. under certain conditions; type `show c' for details.
  265. =head1 OPTIONS
  266. =item A<------------------------------>
  267. =item A<Output format:>
  268. =item B<--all> or B<-a>
  269. Print randomly generated ports & protocols (Default)
  270. =item B<--ports-only> or B<-p>
  271. Print randomly generated ports only
  272. =item B<--dry-gen> or B<-d>
  273. Generate port (& protocol) sequence without trying to connect anywhere.
  274. =item A<------------------------------>
  275. =item A<Port testing:>
  276. =item B<--target-pc> or B<-i>
  277. IPv4 address of the target computer (server). Default: 127.0.0.1
  278. =item B<--protocols> or B<-c>
  279. Use these protocols. Default: tcp,udp (Syntax: tcp or tcp,udp)
  280. =item B<--conn-timeout> or B<-t>
  281. Connection timeout for a port test in seconds. Default: 0.3
  282. =item A<------------------------------>
  283. =item A<Generated values:>
  284. =item B<--min-port> or B<-m>
  285. Minimum port number to be used for knockd daemon. Default: 1
  286. =item B<--max-port> or B<-x>
  287. Maximum port number to be used for knockd daemon. Default: 65535
  288. =item B<--port-seqs> or B<-n>
  289. Number of port (+ protocol) sequences for knockd daemon. Default: 6
  290. =item B<--help> or B<-h>
  291. Prints this help text.
  292. =cut