// Port Knocking Simplified // // by dual_parallel // // http://www.oldskoolphreak.com Humans Really Can Learn ----------------------- Humans learn in different ways. There are four ways, or styles, of learning. These styles are divided into auditory, visual, tactile and kinesthetic learning. This article is a demonstation of kinesthetic, or mechanical, learning. Mechanical learning is not learning by rote. Here it means that one's most effective learning comes from doing. Not simply tactile examples, but hands-on hacking and perserverance. Time to Learn ------------- One day I chose to learn about port knocking. I'll spare you and omit what brought me there. I headed to portknocking.org, where probably everything I wanted to know about port knocking at the time resided. I read the front page. An immediate urge to create struck. So I struck out to create a simplified, if not hacked together, port knocking mechanism. Its creation taught me the basic concepts port knocking, and sharing the process and code with you, the reader, transfers that knowledge - as well as, hopefully, a little inspiration. I gathered the tools needed to own this bit of knowledge. I adore Linux, so that was a given. I can get around in Perl. There's always a little bash, no matter what you do. The target service would be SSH. I did have a need for something that would shut down SSH when not in use, and start it up on demand, reducing exposure. This hack was it. I wanted SSH to be exposed as little as possible, even in a home-broadband environment. There are a few users of this service and no major traffic. There are however many denizens of the Internet that are extremely curious about SSH. At a 30,000 foot view, port knocking is the sending of connection attempts to certain closed ports in a certain order. When a set of requirements is met, the target service, for example, is activated. I do that here in a simpler manner. Walk Through the Code --------------------- All code was authored on Red Hat Enterprise Linux (RHEL) 3. It was tested on RHEL 3 and 4. The code is immediately usable on any recent Fedora or RHEL- derivative distro, like CentOS or Scientific Linux. All scripts are placed in /root/bin. The first thing I needed was a listener. To concentrate on the concepts of port knocking, I chose to use open ports and socat for the listener. socat is essentially netcat++. Plus I had never used it before. I decided to use some high ports that a standard nmap scan wouldn't hit: 59000, 59001, and 59002. A simple bash script called in /etc/rc.local would start three instances of socat and another script discussed later. -------------------------------------------------------------------------------- #!/bin/bash # begin.sh # Start socat listeners /usr/bin/socat tcp-l:59000 localhost & /usr/bin/socat tcp-l:59001 localhost & /usr/bin/socat tcp-l:59002 localhost & # Start socat_monitor /root/bin/socat_monitor & -------------------------------------------------------------------------------- Each socat command starts a TCP listener, tcp-l, on the respective port of localhost. Upon a TCP connect, the listener simply dies. Perfect. Almost. I found Red Hat's firewall tool inadequate. A new iptables script took care of that. Here are the pertinent lines. -------------------------------------------------------------------------------- IPT="/sbin/iptables" $IPT -A INPUT -p tcp -m state --state NEW --dport 22 -i eth0 -j ACCEPT $IPT -A INPUT -p tcp -m state --state NEW --dport 59000 -i eth0 -j ACCEPT $IPT -A INPUT -p tcp -m state --state NEW --dport 59001 -i eth0 -j ACCEPT $IPT -A INPUT -p tcp -m state --state NEW --dport 59002 -i eth0 -j ACCEPT -------------------------------------------------------------------------------- The listeners were working, ports forwarded through a router. Now I had to monitor them. A Perl script, using the Watchdog::Process module, would work nicely. -------------------------------------------------------------------------------- #!/usr/bin/perl -w # socat_monitor.pl ################## # Include Watchdog ################## use strict; use Watchdog::Process; # Define socat processes and # create new Watchdog objects ############################# my $name1 = "socat1"; my $pstring1 = '/usr/bin/socat tcp-l:59000 localhost'; my $proc1 = new Watchdog::Process($name1,$pstring1); my $name2 = "socat2"; my $pstring2 = '/usr/bin/socat tcp-l:59001 localhost'; my $proc2 = new Watchdog::Process($name2,$pstring2); my $name3 = "socat3"; my $pstring3 = '/usr/bin/socat tcp-l:59002 localhost'; my $proc3 = new Watchdog::Process($name3,$pstring3); # Check on socats every minute and # start sshd if they're down ################################## while (1) { if (! $proc1->is_alive && $proc2->is_alive && ! $proc3->is_alive) { system ("/usr/bin/killall socat"); system ("/sbin/service sshd start"); last; } elsif ($proc1->is_alive && $proc2->is_alive && $proc3->is_alive) { ; } else { sleep 300; system ("/root/bin/reset"); } sleep 60; } -------------------------------------------------------------------------------- socat_monitor checks on the three listeners every minute and starts sshd if the right combination is met. Here that is TCP connects to 59000 and 59002 within one minute. sshd is then started the next minute. Any other combination shuts everyting down for five minutes. After that everything is reset with, well, reset. -------------------------------------------------------------------------------- #!/bin/bash # reset.sh # Restart socat listeners /usr/bin/killall socat /usr/bin/socat tcp-l:59000 localhost & /usr/bin/socat tcp-l:59001 localhost & /usr/bin/socat tcp-l:59002 localhost & -------------------------------------------------------------------------------- A major benefit of this exercise is to reduce SSH's exposure. I wanted SSH shut down if no one had logged in within one hour. So if someone had logged in at 7:50 PM, a script would see that when it checked at 8:00 PM and leave SSH on. At 9:00 PM the script would see that no one had logged in within the hour, shut down SSH and initiate the socats and socat_monitor. This script is sshd_monitor and it uses Watchdog::Process as well. -------------------------------------------------------------------------------- #!/usr/bin/perl -w # sshd_monitor.pl ################# # Include Delta_Days and Watchdog ################################# use strict; use Date::Calc qw (Delta_Days); use Watchdog::Process; # Declarations ############## my @secure; my @logins; my $last_login_mon; my $last_login_day; my $last_login_hour; my $mon_to_num; # Define sshd and socat_monitor processes # and create new Watchdog objects ######################################### my $name_ssh = "sshd"; my $pstring_ssh = '/usr/sbin/sshd'; my $proc_ssh = new Watchdog::Process($name_ssh,$pstring_ssh); my $name_mon = "socat_monitor"; my $pstring_mon = '/usr/bin/perl -w /root/bin/socat_monitor'; my $proc_mon = new Watchdog::Process($name_mon,$pstring_mon); # Get script run time ##################### (my $yr, my $mon, my $day, my $hour) = (localtime)[5, 4, 3, 2]; $yr = $yr + 1900; $mon = $mon + 1; my @now = ($yr, $mon, $day); # Open and slurp secure log ########################### if (-s "/var/log/secure" > 0) { open SECURE, "; close SECURE; } elsif (-s "/var/log/secure.1" > 0) { open SECURE, "; close SECURE; } elsif (-s "/var/log/secure.2" > 0) { open SECURE, "; close SECURE; } elsif (-s "/var/log/secure.3" > 0) { open SECURE, "; close SECURE; } elsif (-s "/var/log/secure.4" > 0) { open SECURE, "; close SECURE; } # Shut down everything if there are no logs else { system ("/sbin/service sshd stop"); system ("/usr/bin/killall socat_monitor"); system ("/usr/bin/killall socat"); exit; } # Get time of last login ######################## foreach my $line (@secure) { push (@logins, $line) if $line =~ /Accepted password/; } # Shut down sshd if there are no logins unless (@logins) { if ($proc_ssh->is_alive) { system ("/sbin/service sshd stop"); system ("/root/bin/start_socat"); exit; } if (! $proc_mon->is_alive) { system ("/root/bin/socat_monitor &"); } exit; } my $last_login = pop (@logins); if ($last_login =~ /(^\w{3})\s+(\d{1,2}) (\d\d):\d\d:\d\d/) { $last_login_mon = $1; $last_login_day = $2; $last_login_hour = $3; } if ($last_login_mon =~ /Jan/) { $mon_to_num = 1; } elsif ($last_login_mon =~ /Feb/) { $mon_to_num = 2; } elsif ($last_login_mon =~ /Mar/) { $mon_to_num = 3; } elsif ($last_login_mon =~ /Apr/) { $mon_to_num = 4; } elsif ($last_login_mon =~ /May/) { $mon_to_num = 5; } elsif ($last_login_mon =~ /Jun/) { $mon_to_num = 6; } elsif ($last_login_mon =~ /Jul/) { $mon_to_num = 7; } elsif ($last_login_mon =~ /Aug/) { $mon_to_num = 8; } elsif ($last_login_mon =~ /Sep/) { $mon_to_num = 9; } elsif ($last_login_mon =~ /Oct/) { $mon_to_num = 10; } elsif ($last_login_mon =~ /Nov/) { $mon_to_num = 11; } else { $mon_to_num = 12; } my @then = ($yr, $mon_to_num, $last_login_day); # Compare time of last login and stop # sshd and start the socat listeners # if no logins w/in the last hour ##################################### my $diff = Delta_Days (@then, @now); if ($diff >= 1) { if ($proc_ssh->is_alive) { system ("/sbin/service sshd stop"); system ("/root/bin/start_socat"); exit; } if (! $proc_mon->is_alive) { system ("/root/bin/socat_monitor &"); } } elsif ( ($hour - $last_login_hour) > 1 ) { if ($proc_ssh->is_alive) { system ("/sbin/service sshd stop"); system ("/root/bin/start_socat"); exit; } if (! $proc_mon->is_alive) { system ("/root/bin/socat_monitor &"); } } -------------------------------------------------------------------------------- sshd_monitor looks for logins from /var/log/secure. If no data exists in the secure logs, something fishy is going on and everything shuts down. Date::Calc is used to compare login times. As stated, sshd_monitor is called each hour from cron. -------------------------------------------------------------------------------- 00 * * * * root /root/bin/sshd_monitor -------------------------------------------------------------------------------- The last piece was a script to start up the listeners and socat_monitor if no one had indeed logged in within an hour. start_socat is also called if there were no logins in secure logs of greater than zero size. -------------------------------------------------------------------------------- #!/usr/bin/perl -w # start_socat.pl ################ # Include Watchdog ################## use strict; use Watchdog::Process; # Define socat and socat_monitor processes # and create new Watchdog objects ########################################## my $name = "socat_monitor"; my $pstring = '/usr/bin/perl -w /root/bin/socat_monitor'; my $proc = new Watchdog::Process($name,$pstring); my $name1 = "socat1"; my $pstring1 = '/usr/bin/socat tcp-l:59000 localhost'; my $proc1 = new Watchdog::Process($name1,$pstring1); my $name2 = "socat2"; my $pstring2 = '/usr/bin/socat tcp-l:59001 localhost'; my $proc2 = new Watchdog::Process($name2,$pstring2); my $name3 = "socat3"; my $pstring3 = '/usr/bin/socat tcp-l:59002 localhost'; my $proc3 = new Watchdog::Process($name3,$pstring3); # Restart socats if any are down ################################ if (! $proc1->is_alive || ! $proc2->is_alive || ! $proc3->is_alive) { system ("/root/bin/reset"); } # Start socat_monitor if it's not running ######################################### if (! $proc->is_alive) { system ("/root/bin/socat_monitor &"); } -------------------------------------------------------------------------------- Recap ----- From boot, begin is called in rc.local starting the socat listeners and socat_monitor. socat_monitor checks on each listener each minute and kills the remaining listener, starts sshd and exits if the right combination is met. It also locks up for five minutes and calls reset if the wrong combination is met. sshd_monitor, called each hour by cron, looks for the running sshd process and shuts it down and calls start_socat if no one has logged in that last hour. Lessons Learned --------------- At first I didn't have a penalty for wrong or random connects, so I tacked on five minutes. Of course it doesn't mean much for the massive three-factorial combinations of this simple example. It does for more complex knocks. Speaking of complex knocks, a bash script can automate knocking, making complex port connects simple. You can also provide such a script to users. GnuPG it up and send it off whenever you change the knock. For this example: -------------------------------------------------------------------------------- #!/bin/bash /usr/kerberos/bin/telnet HOST 59000 /bin/sleep 1 /usr/kerberos/bin/telnet HOST 59002 -------------------------------------------------------------------------------- Also, sshd_monitor does a little more than stated above. I oringinally didn't account for various processes dying unexpectedly. It now handles upkeep of the other associated processes. I was well aware that there is no security through obscurity before wrtiting this article. This is addressed beyond the front page of portknocking.org, and I do not, nor should you, count on this instance of port knocking, or any other, to secure a service. Although, even using open ports, the above port knocking implemenation dramatically reduced the number of attacks upon the target service. The most important lesson learned is that self-sufficiency is the penutltimate of hacking. Hacking is not about buying the latest gadget. Nor is it using code you can't see, learning curtailed at the end of the clicks. It is the do-it- yourself attitude that is the ultimate pay-off to the hacker. If you need or are intriqued by something, dive in. Especially if it's "beyond you." Determine how you learn best and extend the possibilities of your favorite certain technology. It is then you will extend yourself. So many thanks to one of my best friends, bland_inquisitor.