Sign in to follow this  
Followers 0
dual

Port Knocking Simplified

10 posts in this topic

Port Knocking Simplified

by dual_parallel

http://www.dualisanoob.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, "</var/log/secure" or die "Can't open log: $!";
 @secure = <SECURE>;
 close SECURE;
}
elsif (-s "/var/log/secure.1" > 0)
{
 open SECURE, "</var/log/secure.1" or die "Can't open log: $!";
 @secure = <SECURE>;
 close SECURE;
}
elsif (-s "/var/log/secure.2" > 0)
{
 open SECURE, "</var/log/secure.2" or die "Can't open log: $!";
 @secure = <SECURE>;
 close SECURE;
}
elsif (-s "/var/log/secure.3" > 0)
{
 open SECURE, "</var/log/secure.3" or die "Can't open log: $!";
 @secure = <SECURE>;
 close SECURE;
}
elsif (-s "/var/log/secure.4" > 0)
{
 open SECURE, "</var/log/secure.4" or die "Can't open log: $!";
 @secure = <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.

0

Share this post


Link to post
Share on other sites

Correct me if I'm wrong, but doesn't socat open up a port?

And shouldn't port knocking merely log when invalid connection attempts have been made to the (closed) ports you waiting for knocks on?

Or am i mistaken and socat doesn't actually open up the port?

[EDIT]:I haven't looked into it yet, but maybe its possible to get iptables to fork a process when the ports you're waiting for knocks on get knocked, and the alert your daemon that something knocked. Or send some kind of signal to your daemon. Probably not, but I don't have a great knowledge of iptables.....

Edited by kuza55
0

Share this post


Link to post
Share on other sites

Awesome article, though im not a nix user really and have no idea really what thats about. Though from what i make of it, it seems like an awesome work of art! =)

0

Share this post


Link to post
Share on other sites

#!/usr/bin/perl -w

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

I think the first if should have "! $proc2->is_alive", shouldn't it?

Anyway, cool article. Your code does make it pretty simple. :voteyes:

0

Share this post


Link to post
Share on other sites

Thanks for the input and kind words!

You're right, kuza55, I used the concept of port knocking, not a strict definition. Your questions are answered in more detail in the article.

I think the if is right, ntheory. If procs 1 and 3 are dead from connects to their associated ports, sshd is started. If there are any connects to 2, sleep and reset. I will definitely double-check. Stay up, playa! <= ripped from natas

0

Share this post


Link to post
Share on other sites
You're right, kuza55, I used the concept of port knocking, not a strict definition. Your questions are answered in more detail in the article.
Alright, I was just checking, sicne I haven't played with socat myself, so yeah....

Good article nonethless, though.

I did some more reading/thinking on how to get iptables to alert you, and there isn't much of a way except maybe routing the packets to some other ports which are blocked which you have listeners on. But an easier way is to get iptables to log connection attempts on the ports you want to listen on, and the parse the log. The other way I read about was to sniff the incoming connection packets coming to your server and parse them.

I got pointed to this article as well: http://www.linuxjournal.com/article/6811 which is rather useful.

Another possible idea is to knock with different packets, so you might send a SYM to one port a FIN to another and NULLs to 2 others and then an ACK followed by a SYN to the same port, etc, which provides for more possibilities per port. But you would have to do either some complex configuration or have a sniffer, where (IMO) having the sniffer would end up being easier int he end.

0

Share this post


Link to post
Share on other sites

Cool writeup, thanks for the work!

I tried to implement something like this some time ago, but instead went with dialaport.

My Asterisk box has an ssh key for my ssh server and I can call a secret extension to toggle it on or off.

0

Share this post


Link to post
Share on other sites

Interesting process, a bit different than traditional port knocking. (I also read the article in Blacklisted 411.)

A potential vulnerability would be if something other than a normal connect causes the socat processes to terminate. Perhaps your "knock" should include some sort of additional information to be incorporated? (Some additional way to verify the process died in the intended way.)

0

Share this post


Link to post
Share on other sites

Yeah, the scripts should take care of processes dying/being killed, though two instances they haven't. Authenitcating the knock is a great idea.

The Asterisk idea owns. Asterisk changes everything. Which is rad.

0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0