Here is a small set of BASH scripts you can take and use to set up your own single-packet authentication, and then customize it to your heart's content. I believe it is leaner and more secure the the available packaged solutions.

[Edit: note that the port-randomization concepts demonstrated in this post have been incorporated recently into fwknop as a command-line option, and the author has kindly credited this thread for the idea.]

Traditional port-knocking is vulnerable to replay attacks. While good single-packet authentication tools are not vulnerable to replay, they ARE vulnerable to piggy-back exploits. Some port-knocking tools install thousands of lines of code and dozens of perl modules and other dependencies. A couple hundred lines of BASH can do the job, give you complete flexibility, and reduce your vulnerability to scripts designed to attack common configurations. Analyzing and customizing this script for your needs should also be a good intermediate-level BASH learning exercise (please share your improvements).

The client script uses:

openssl - used to encrypt a tiny unique one-time message

hping - used to construct and send a packet containing the encrypted message

The "daemon" script uses:

tcpdump - listens (outside the firewall) for the very special packet, and captures it

openssl - decrypts the packet's payload

What's different about this approach is that it knocks on a random port every time (one of about 28,000) and makes the service connection on a random port every time. Some SPA solutions (not fwknop, any more) currently knock on one port -- the same port every time. That makes it pretty easy to monitor. And they do not randomize the ports used for service connections, which makes it very easy to simply piggy-back in on the open connection simply by spoofing the connecting client's address and banging away on the predictable port until it opens.

Aside from handling that randomization, this tool more or less imitates what is done by similar open source and commercially-available tools. The script uses encrypted information in the knock packet to decide how to insert or append iptables rules, opening a firewall port for a specific IP address only, connecting to a specific service behind the firewall, for a period of a few seconds only for a connection to be established, and then closing said port. The ongoing connection is maintained automatically by netfilter connection tracking and the port continuously appears to be closed to all other addresses, even while our client is connecting.

Please feel free to tell me how terrible my script is, as long as you tell me what I need to do make it better. : )

I call the client "Ramius", because like the submarine captain of that name in "Hunt for Red October", it sends "one ping only" (you must have seen the movie to appreciate this). Correspondingly, the daemon is called "hydrophone" because it listens for the ping.

The client consists of two scripts:

ramius: the executable script shared system-wide among any users on the machine (run using su or sudo so hping can access a raw port); this belongs in /usr/loca/bin or someplace like that

ramius_agent: a per-user extension script that runs with user privileges to initiate the actual service connections using the desired interface (e.g., ssh via shell or nautilus). Each user gets their own copy of ramius_agent so they can customize if desired, and a per-user configuration file; these go in ~/.ramius.

Similarly, the daemon consists of two scripts:

hydrophone: basically a loop that listens for a packet using tcpdump, then parses the packet, then dispatches the airlock script to take action. Hydrophone is run as a daemon, launched by an initscript.

airlock: instantiated by hydrophone to handle connections once a packet has been accepted and parsed. Airlock instances are run in parallel to hydrophone, so that multiple connections can be handled in rapid sequence if necessary. Airlock opens the port (inserts IPTables rules) waits a configurable period of time, and then closes it (removes IPTables rules).

It's easiest to understand by seeing the client first, so you understand what the daemon is decrypting and parsing.

First, a quick peek at the per-user config file. You could easily make any of these items command-line parameters if you prefer:

This is the main client script. It sources the config file (above) of the user who runs the script (using su or sudo), and as noted above, it also calls the small per-user extension script "ramius_agent".

Note: this proof-of-concept version uses a symmetric 256-bit AES-cbc cipher to simplify authentication and produce a relatively small encrypted payload. To avoid packet fragmentation, overall packet size must be smaller than the path MTU. The code here creates a conservatively small 128-byte payload (156-byte packet), which is well below path MTU in most scenarios. Although this aes256-based implementation provides a very high degree of security, mature implementations might use larger asymmetric ciphers (e.g. X.509 certs or GPG with keys as large as 1024-2048 bits) while remaining useful in PMTU >=576 settings. A network of at least ethernet grade under unified control will typically have an MTU of 1500 bytes, but I see no use for SPA payloads that large. So what you see here works, and it's secure, but you can "bump it up" if you want.

This is the small extension script, found in the user's home directory (~/.ramius/ramius_agent):

And this is the companion module that the daemon dispatches instances of to take action in parallel while it returns to listening. If multiple packets are accepted in a short period of time, there will be multiple instances of this running in parallel.

Note: You should not try to implement this blindly -- this code here must fit your iptables rules and you will need to customize it. Specifically, make sure the rules are going into the right chains (I have it set up below to insert into the chains that are created by shorewall -- while the tables should be the same, if you don't use shorewall, you won't have these particular chains.) Where it says, "/sbin/iptables -t nat -${ACTION} net_dnat -s ${IP} -p tcp -m tcp ", you will have something else instead of "net_dnat". And where it says, "sbin/iptables -t filter -${!ACTION} net2fw ${RULE} -s ${IP}", you will have something else instead of "net2fw". The phrases "net_dnat" and "net2fw" are the names of two of my iptables chains. You will have the names of your iptables chains within the nat and filter tables respectively into which the rules should be inserted or appended.

Code:

#!/bin/bash

# /root/bin/hydrophone/airlock
# BoneKracker
# Rev. 16 March 2008

# Purpose: used by hydrophone to open and close ports by appending ("A")
# or inserting ("I") iptables rules, and then deleting ("D") them
# after waiting a prescribed time.

##### Configuration
SERVER_IP="192.168.1.10"

##### Parameters
# Cannot simply inherit these as a subshell, because their value
# in main script may change during this script instance's "wait" event.
TARGET=$1 # a code for the host (behind our firewall) the client wants access to
IP=$2 # the IP address of the requesting client
SERVICE=$3 # a code for the host's service the client wants access to
PORT=$4 # the external port the client will connect to
HOLD=$5 # number of seconds to wait before closing opened port

##### airlock-event function

# IPTables uses actions "-A", "-D", and "-I" (i.e. Append, Delete, Insert)
# In this file, we will indirectly substitute "I" for "A" when
# the rule should be inserted instead of appended
# - where "append" is appropriate use the parameter: ACTION
# - where "insert" is appropriate use the parameter: !ACTION
# (If this confuses you see the BASH man page and search for "indirection".)
A="I"; D="D"

## map services to internal ports
# Note: because this script causes all connections to be rewritten by DNAT or
# REDIRECT, all hosts -- including the firewall -- can use the same ports.
# (If that doesn't suit you, add a $TARGET layer to the case block.)
case $SERVICE in
"f" ) INT_PORT="21";;
"h" ) INT_PORT="22";;
esac

5. Because ramius connects with a different port each time, we ask the
ssh client not treat each new port as a new host it must track. We do
this with an entry in ~/.ssh/config for each firewall you knock on.

If you don't do this, you will be prompted every time to press 'y' to accept
the remote host's key. Because of the large range of ports this script uses,
a single remote host could theoretically have over 28,000 keys consuming
10.8 MiB. Refer to SSH_CONFIG(5) for more information.

This sacrifices SSH's host verification logic, which I believe is a small loss relative
to the security against piggy-backing gained by randomizing ports, especially given
the use of public key authentication for the SSH connection, which somewhat
obviates host verification.

Oh, and of course we need an init script to launch the "daemon". The hydrophone script is not a proper daemon, so this is a bit of a hack. Suggestions are welcome with regard to making the script more properly "daemonized":

Thanks very much for coming up with this - it addresses a number of concerns I've had about port knocking for a while.
I finally got around to trying to implement your scripts today, but I keep getting this error:

I'm really not sure how to resolve this. Do you have any ideas?
EDIT: It seems that there are some variables that aren't being set: FW_LO, and FW_HI. How are these configured on your system?
EDIT2: I finally got this beast working. I added the FW_LO and FW_HI variables to ramius.conf, I had to remove the -a flag from hping, I had to patch hping to work with my wireless card, I had to remove the ${RULE} logic from the airlock script and change A to I to insert the rule at the beginning of the chain instead of trying to append it, and I had to change the ${!ACTION} to $ACTION in the airlock script to get the filter rule set properly._________________

aidanjt wrote:

You see, instead of arguing from ignorance, and fear, there is only one way to verify a theory. And that's not by clutching a black book and begging the sky fairy for deliverance from the mad scientists and their big machines.

Sorry I didn't respond in time to be of help. Thanks for trying this, and I'm glad it works for you.

It appears that I did indeed fail to copy the entire ramius.conf file when I cut-and-pasted it in here, and these lines were missing:

Code:

# firewall's "local port range"
FW_LO=32768
FW_HI=61000

I have added them into the original post.

As I noted, this is a proof-of-concept you can build upon. Now that you've troubled yourself to look at the code, I'd be interested to hear of any variations you make (and any other problems you encounter -- especially if you fix them like this one).

Quote:

I had to remove the -a flag from hping

Yeah, spoofing won't work because the server-side script opens the firewall only for the IP from which it thinks the packet came. That's why -a is not in the options I used.

Quote:

, I had to patch hping to work with my wireless card, I had to remove the ${RULE} logic from the airlock script and change A to I to insert the rule at the beginning of the chain instead of trying to append it, and I had to change the ${!ACTION} to $ACTION in the airlock script to get the filter rule set properly.

Hping seems like a bit of a hack, and I'd rather use something else if anybody has a good idea. This seemed like the simplest, but I'm not so sure about its quality/documentation, etc. For example, there's a persistent and meaningless error so it always exits with a non-zero status, which is why I had to ">/dev/null" its output and did not perform any test to see if it succeeded.

One of the reasons I put the "airlock" stuff in a separate script was because it's the portion that is most likely to require customization. Congratulations on working it out. You have to know a bit about IPTables to be able to do that. The !ACTION vs. ACTION thing was supposed to be an easy way to toggle between "appending" vs. "inserting", but indirect substitution is pretty confusing, so whatever works for you and your rule set is good.

Right now I'm trying to figure out why mine is failing intermittently after a rebuild of my desktop (moved to ~x86). It looks like the packet is no longer getting properly decomposed. It works for a while and then tells me it can't do math on the timestamp if the timestamp is " ". So I suppose it's possible that some tool or encryption changed. I'll have to take a look at the tempfile on the sending and receiving sides to see if I need to modify some bit of code (sed, awk, cut, etc.). Either that or go back to "freeballing it".

Yeah, spoofing won't work because the server-side script opens the firewall only for the IP from which it thinks the packet came. That's why -a is not in the options I used.

Hmm. Then I'm not sure how it got into mine in the first place, as I copied and pasted it verbatim from this forum post. *shrug*.

BoneKracker wrote:

Right now I'm trying to figure out why mine is failing intermittently after a rebuild of my desktop (moved to ~x86). It looks like the packet is no longer getting properly decomposed. It works for a while and then tells me it can't do math on the timestamp if the timestamp is " ". So I suppose it's possible that some tool or encryption changed. I'll have to take a look at the tempfile on the sending and receiving sides to see if I need to modify some bit of code (sed, awk, cut, etc.). Either that or go back to "freeballing it".

I've noticed this a couple of times too. Usually a second or third attempt gets through, so I'm not sure exactly what's going wrong. I may put my debugging echoes back in on both ends to see what's being sent, and what's being received.
As far as the hackiness of hping, I agree after looking at its code. An example being that it determines which physical link layer header size to use based upon the name of the interface (eth/ppp/br/wlan, etc). However, I'm not aware of another way to assemble custom packets like this and I don't really want to go back to knockd
I forgot to mention yesterday - the lines dealing with $BUFFER and $HISTORY in hydrophone needed some tweaking. Specifically,

The rest of the script treats $BUFFER and $HISTORY as files, but this portion of the script creates a directory named /var/tmp/hydrophone/packet_buffer%/*/ and one named /var/lib/hydrophone/packet_history%/*/. Those aren't wildcards - those are actually directories named "*". I'm not sure what the original intention of these lines was, but I changed them to

With regard to changes and tweaks, later today I'll be working on a version of ramius that uses the same static port (443) for both PING_PORT and SVC_PORT. The motivation is use behind a very strict http proxy that won't allow outgoing packets on just any port. I'll still have the security of passphrase-encrypted random packets and replay protection, so the loss of random ports isn't particularly troublesome to me._________________

aidanjt wrote:

You see, instead of arguing from ignorance, and fear, there is only one way to verify a theory. And that's not by clutching a black book and begging the sky fairy for deliverance from the mad scientists and their big machines.

This has the same effect as the code below, but avoids use of the external tool 'dirname' (and is therefore a hair faster and not dependent upon the tool's availability). For that reason, I try to avoid the use of external tool when a built-in can do the job:

If you want to demonstrate this to yourself try the following at the BASH prompt:

Code:

TESTFILE="/var/tmp/testfile"
echo $TESTFILE
echo ${TESTFILE%/*}

What you did with 'touch' is fine. But since the server script is run by root, the directories can be assumed writable if they exist, so the time needed to write the file can be avoided.

With regard to using static ports (in ramius):

I had considered putting in a configuration option where the user can choose a static port in the config file (and if the port is not defined, it assumed that one should be randomly chosen). While the port randomization adds security, it has the down-side you mentioned as well as the fact that SSH was designed with the assumption of static ports ("known_hosts" are tracked by hostname/address as well as port number).

So if you do decide to switch it to static ports, you might want to consider the approach of making it a configurable option. Although, if you're going to use static ports, you might want to just use one of the available packages.

Edit: Oh, and if you do go to a static port for ssh, you can get rid of the ~/.ssh/config file I suggested.

Last edited by Bones McCracker on Tue Jun 03, 2008 5:47 am; edited 2 times in total

So I am rewriting that sed statement to eliminate literal line number references. Here is what I'm currently trying, although it seems to be intermittently causing its own mangling, resulting in a different openssl error ('bad magic number' instead of 'error reading input file'):

That says, "trash the first line, then within what's left (without touching the last line) eliminate all occurrences of some char(s) followed by dot(s), then output what's left including the last line." I suck with sed - learning as I go.

I'd like to avoid extracting the string based on its specific position within the overall packet text and without relying on the fixed prefix that it begins with. These would change if someone chooses to use a different encryption algorithm or a different packet type. So I need to simply eliminate everything but the encrypted string (where the dots end), leaving the rest on its natural two lines.

Apparently my current attempt is somehow altering the string's contents itself; while I have eliminated the original error "error reading input file" caused by the extra line break, I am now getting another openssl decrypt error "bad magic number" (which is similar in that you see it when you feed openssl something it can't decrypt).

Plus, the old error is still there in the rare event that the extra line break is preceded immediately by a character and not a dot:

I'm also getting the "bad magic number" error on occasion, so I don't think it's due to your sed-fu. I'm afraid I won't be much help with sed, as my regex is not very good at all.
In the current implementation, if the packet is somehow damaged (bad magic number or something else that causes it to be incompletely parsed), hydrophone crashes completely and renders the box remotely inaccessible. I think we need to come up with a way to protect against that to make the script more robust, but I haven't come up with a workable solution yet._________________

aidanjt wrote:

You see, instead of arguing from ignorance, and fear, there is only one way to verify a theory. And that's not by clutching a black book and begging the sky fairy for deliverance from the mad scientists and their big machines.

I probably need to split that complex statement out into its three parts instead of using pipes. Then I could just log the error and jump out of the function on a non-zero exit code. That would keep the server-side script from crashing.

Notifying the client of the failure would make it something other than "single-packet" authentication and would require a listener on the client side. I don't think we want to go there. The client's connection attempts should perhaps have a limited time-out parameter if that can be included as a command-line option.

My intuition tells me the "bad magic number" problem I am having now is not caused by transmission error (i.e., it's not the occasional bad packet somehow mangled in transit). I was using this basic setup successfully for a month or so without that problem. Then, suddenly it's experiencing this. Although it could be a bug in hping or something, its more likely my own bad programming.

Right now, after implementing the revised sed statement, a half dozen trials show it seems to succeed on the first connection every time, and then fail (bad magic number) on the subsequent connection every time. At that point I went to bed and haven't got back to it yet. That might be something simple like bad programming -- a variable that must be reset each time or something like that.

If you are actually interested in putting some thought into this, in addition to this problem here are two other pretty core-level items that I could use somebody else's brain on:

1. I am running tcpdump one packet at a time. It's really designed to run and run. An alternative approach would be to run tcpdump continuously side-by-side with another process that receives the captures packets and then spawns the packet decomposition function in a separate thread for each packet captured. I was afraid to do that because I didn't want to end up with multiple processes trying to implement changes to the IPTables at the same time. Theoretically though, this kind of approach should work because of locking mechanisms internal to netfilter.

Tcpdump could write out to a FIFO, and then a separate loop (a peer process, if you will) could read the FIFO and fork subprocesses to perform packet decomposition (in other words, move the decomposition function into what is now the "airlock" script). In one of my very early prototypes, I think I was running tcpdump on a continuous basis like that, but I changed to a single-packet approach at some point for reasons I no longer recall.

2. The "packet history", which is checked to see if the incoming packet's hash has been received before, is currently just a log that grows indefinitely. It needs to be limited to a certain size or time period, maybe with the oldest packets being removed as new ones arrive. One idea that occurred to me is a ring-buffer of sorts, where a new file is created daily or weekly and the files are rotated. Maybe there's a better way. I wonder if a tiny database of some sort would be more efficient. The simplest approach would be to occasionally check the length of the file (as in wc -l) and delete the first 10 lines or whatever when it gets too long. I just haven't got round to giving this any thought yet.

Okay. Problem fixed. As far as a couple-dozen rapid-fire connections show, it now works consistently.
It was a combination of things. These are the changes I made:

1. Added the -A argument to the 'openssl enc' command to keep its output to a single line (which simplifies the task of parsing it from the surrounding packet text).
2. Modified the sed statement accordingly.
3. Put the value of $PASSARG in single-quotes instead of double-quotes.

The last one is a bit mysterious. I discovered the need to do this by interactively passing an extracted message string to the openssl decryption command used in the server script. It gave errors when the passarg was only in single quotes, apparently becoming confused by the exclamation point I had chosen to use in the actual passphrase. I thought this was probably just due to the fact that I was trying it in an interactive shell, but when I made the same change to the actual script, the behavior of "subsequent connections" failing disappeared.

So I will add the changes to the original post. Note that this change to the sed statement supersedes the one highlighted in the diff I provided above (that one can be ignored). For your convenience, here is a diff:

This change broke hydrophone for me. Removing the -A from the openssl command restored functionality. The error was a rather vague "error reading input file"._________________

aidanjt wrote:

You see, instead of arguing from ignorance, and fear, there is only one way to verify a theory. And that's not by clutching a black book and begging the sky fairy for deliverance from the mad scientists and their big machines.

Ah, sure enough that was the issue. Adding -A on both sides made it work _________________

aidanjt wrote:

You see, instead of arguing from ignorance, and fear, there is only one way to verify a theory. And that's not by clutching a black book and begging the sky fairy for deliverance from the mad scientists and their big machines.

And here is the fix for the endlessly-growing packet history file. Be forewarned I was not in a position to test this, but it's fairly trivial so I hope it's ok. Let me know if you have problems.

Instead of a $HISTORY file, we now have a $DATA directory (to accomodate any future modifications that might also require persistent storage).

In the $DATA directory (i.e. /var/lib/hydrophone), there will be three history files, which will be rotated:
history.0
history.1
history.2

The hash of an incoming packet is appended to history.0.
When history.0 line count exceeds a threshold, the files are rotated like:
mv history.1 history.2
mv history.0 history.1

The incoming packets are compared to ${DATA}/history.* for uniqueness.

That "threshold" is one-third of $HIST_SIZE, which is configurable (the total number of records to keep). I have no idea what to set this to. I set it conservatively low (1500), but I suspect you could have tens or hundreds of thousands of records without any real performance problem (checking is a simple grep, and on the rare occasion that rotation is required, it will occur in parallel with the "airlock" script (the IPTables manipulation)). I imagine though that in reality most replay attacks would attempt to use a packet while fresh, probably even instantaneously upon receipt, so it might be quite realistic to set this number much lower, like 100 or so.

Here's the diff (it looks big because I changed a variable name and also moved a block of code). This is also in the original post if you want to just grab that:

I was still getting intermittent failures on the payload function, so I took a closer look at it and asked for some help from a friend whose bash-fu is far greater than my own. This is what we came up with, as a substitute for all the sed work and piping:

I've tested it several times, and it seems to work more consistently for me. Your thoughts?
Also, I still think that we need a way to protect against garbage packets - right now if someone were to send a mangled hping (wrong passphrase or some other cause), it would (and does) crash hydrophone immediately and completely. I /think/ that's what the || exit $E_CRYPT above is supposed to accomplish, but if so something is going wrong. Any ideas on this?
[EDIT]I also have a modified version of /etc/init.d/hydrophone:

You see, instead of arguing from ignorance, and fear, there is only one way to verify a theory. And that's not by clutching a black book and begging the sky fairy for deliverance from the mad scientists and their big machines.

I was still getting intermittent failures on the payload function, so I took a closer look at it and asked for some help from a friend whose bash-fu is far greater than my own. This is what we came up with, as a substitute for all the sed work and piping:

I've tested it several times, and it seems to work more consistently for me. Your thoughts?

Excellent! Thanks for the help -- and thank your friend too.

I had thought about using ${foo##bar} parameter expansion instead of sed, but didn't think it would work because of the line break at the end of the first line of the packet text that tcpdump outputs. If it works, let's use that that since it's more readable and also uses only built-ins instead of external tools.

UberPinguin wrote:

Also, I still think that we need a way to protect against garbage packets - right now if someone were to send a mangled hping (wrong passphrase or some other cause), it would (and does) crash hydrophone immediately and completely. I /think/ that's what the || exit $E_CRYPT above is supposed to accomplish, but if so something is going wrong. Any ideas on this?

Yes, I agree. Although I am no longer having this problem since revising that statement (the -A change), it is definitely too fragile, and your problem demonstrates the need.

The statement your friend enhanced was originally three statements. I piped them together (which violates readability/simplicity) because I thought that run faster than the original three statements (which must othersie pass the data as either a variable or a tempfile or fd). The $E_CRYPT is just one of several exit codes assigned centrally in another location for debugging convenience. $E_CRYPT was originally at the end of the openssl statement when it was alone on its own line. Its still there because my intuition told me the openssl statement needed some kind of error trapping, and I intended to follow up later.

When I debugged that complex statement the other day, I broke it back out into the three individual statements. I think what we really want to happen on a "bad packet" is merely return (terminating the decompose function and returning to listening, maybe logging a "bad packet" error). Although your friend left all the pipes in place, I think we may need to break it back out to an extent. So, in short, what do you think of the following:

This is great; thank you. I was hoping somebody with a better understanding of gentoo's init scripts would do something with this. I had originally created an initscript using the normal "start-stop-daemon" that looked quite similar, but I had problems making it work. I figured this was because the hydrophone script itself was lacking some of the normal behavior of a "daemon". I tried to add some of this (publishing the PID), but I think it's still missing things like error-trapping on the event of an "exit" or in the case of a "crash", that returns control to the parent process (the initscript). I gave up and went with the crude initscript I originally included. Do you think anything else needs to be added to the hydrophone script to make it more of a proper "daemon"? For example, I am vaguely aware there should be some kind of error trapping that occurs when it crashes so the parent process knows and can set the service status accordingly.

Have you tested the initscript to the extent that I should replace the one in the original post?

The original post regarding the idea to send SPA packets over random ports as well as creating NAT rules for incoming connections
over random ports is a good idea. I have implemented these ideas within the 1.9.4 release of fwknop, and credited BoneKracker with
the ideas:

The original post regarding the idea to send SPA packets over random ports as well as creating NAT rules for incoming connections
over random ports is a good idea. I have implemented these ideas within the 1.9.4 release of fwknop, and credited BoneKracker with
the ideas:

The port randomization is really just an additive measure I felt would reduce the likelihood of detection, sniffing and a resulting piggy-back intrusion. Most networks can be sniffed, and it is trivial to perform packet capture/analysis and spoofing. So the vulnerability is real, if somewhat obscure and only accessible to a hacker with at least some minimal skill and a real desire to break into your particular firewall.

While the randomization does not technically eliminate such risk, it makes it statistically very unlikely as well as enormously more difficult to perform. While a firewall is (hopefully) just one layer in your security "stack", it is better if there's not a hole in that layer.

I should add that it's my opinion that fwknop is a great tool and the obvious choice for linux users (besides anyone with an oddball interest in creating a "grow your own" solution).

I did find one drawback to port randomization (specific to ssh) worth noting:

Ssh is not designed with idea in mind of randomizing the connection port. Some of its security features assume a connection from a given client will always originate from the same port. One work-around is demonstrated in the client-side "ramius connector" extension script and it's installation notes (i.e., the use of a ramius-specific ~/.ssh/config file prevents the ~/.ssh/known_hosts file from receiving a new entry for every new connection and becoming unnecessarily large).

The port randomization is really just an additive measure I felt would reduce the likelihood of detection, sniffing and a resulting piggy-back intrusion. Most networks can be sniffed, and it is trivial to perform packet capture/analysis and spoofing. So the vulnerability is real, if somewhat obscure and only accessible to a hacker with at least some minimal skill and a real desire to break into your particular firewall.

While the randomization does not technically eliminate such risk, it makes it statistically very unlikely as well as enormously more difficult to perform. While a firewall is (hopefully) just one layer in your security "stack", it is better if there's not a hole in that layer.

If the concern is about an attacker that can sniff the network, why does randomizing the destination port for the follow-on connection (say, SSH) make a piggy-back attack more difficult to perform? If the attacker can sniff traffic, it is trivial to watch for the outbound SSH connection over *any* port, and it doesn't matter if SPA is used (except of course that an attack against a userspace vulnerability in the remote SSH daemon can only be accomplished over an established TCP connection, which the attacker cannot establish when spoofing a SYN packet - hence SPA is still useful). If you mean that the port randomization feature makes it harder for those attackers that are not expecting SSH connections over anything but port 22 and are therefore just not watching for this, then sure I agree. IMHO, the main benefit of port randomization is that it adds an element of unpredictability for those who are analyzing networks with certain assumptions in mind, but watching for SSH connections over a non-standard port is easy.

BoneKracker wrote:

I should add that it's my opinion that fwknop is a great tool and the obvious choice for linux users (besides anyone with an oddball interest in creating a "grow your own" solution).

Thanks.

BoneKracker wrote:

I did find one drawback to port randomization (specific to ssh) worth noting:

Ssh is not designed with idea in mind of randomizing the connection port. Some of its security features assume a connection from a given client will always originate from the same port. One work-around is demonstrated in the client-side "ramius connector" extension script and it's installation notes (i.e., the use of a ramius-specific ~/.ssh/config file prevents the ~/.ssh/known_hosts file from receiving a new entry for every new connection and becoming unnecessarily large).

Agreed, although this seems like it might be a candidate for using the -o option on the ssh client command line. Also, a modified version of my OpenSSH/fwknop integration patch could be developed without the need for a custom config:

If the concern is about an attacker that can sniff the network, why does randomizing the destination port for the follow-on connection (say, SSH) make a piggy-back attack more difficult to perform? If the attacker can sniff traffic, it is trivial to watch for the outbound SSH connection over *any* port, and it doesn't matter if SPA is used (except of course that an attack against a userspace vulnerability in the remote SSH daemon can only be accomplished over an established TCP connection, which the attacker cannot establish when spoofing a SYN packet - hence SPA is still useful). If you mean that the port randomization feature makes it harder for those attackers that are not expecting SSH connections over anything but port 22 and are therefore just not watching for this, then sure I agree. IMHO, the main benefit of port randomization is that it adds an element of unpredictability for those who are analyzing networks with certain assumptions in mind, but watching for SSH connections over a non-standard port is easy.

Yes, that's the essence of it. But also, I think it's a necessary multiplier of the value of using a restricted time window. I'm going somewhat on instinct here so forgive me for not explaining my (possibly invalid) thinking more clearly earlier. Please do correct me where I'm wrong.

The value of only opening a port for a brief window of time (e.g. 30 seconds) is that it's not enough time for most people to identify the connection, identify the address of the connecting client, set up to spoof the address of the connecting client, interdict the client (if necessary), and connect. However, if the port never changes, this is somewhat invalidated.

In the fixed service port scenario, once the attacker has detected and monitored one connection, they know a lot of useful information. They know the source and destination addresses, they know the ports. With this information they can:

- attempt a simple* piggy-back attack any time they detect a connection simply by spoofing the address:port (which they know)
- efficiently capture future SPA packets themselves, which they can then analyze to attempt to identify the algorithm in use
- zero in on subsequent connections to footprint the operating systems and select a sequence number prediction algorithm

Now, granted, in the case of SSH in particular, a single connection attempt (or even a 30-second window) is of limited value. But the SPA should be able to serve a wide variety of protocols, many of which may be less secured. And as I've said, having multiple layers doesn't mean a hole in one is acceptable.

*Also granted, a properly-constructed, stateful firewall will drop the spoofed connection's SYN attempt if it arrives after the legitimate client's. But if it arrives first, the attacker will have successfully interdicted the client.

To arrive first, the attacker would require an automated attack set up on a hair trigger to launch when the SPA packet is detected. They don't have time to filter thousands of packets across thousands of ports, identify the SPA connection, capture the packet, extract the port number, configure to spoof the address and port, and launch a connection. Even if automated, this will take enough time to make a difference.

But, if they know all that information in advance, I think they **do** have time. When the first packet -- any packet -- passes the sniffer with that address and port, the sniffer automatically triggers a burst of connection attempts. They are at least as likely as the legitimate client to successfully pass through the firewall.

Furthermore, the same time constraints make a more sophisticated attack involving interdiction via sequence number prediction more difficult as well.

michaelrash wrote:

Agreed, although this seems like it might be a candidate for using the -o option on the ssh client command line. Also, a modified version of my OpenSSH/fwknop integration patch could be developed without the need for a custom config:

Yes. That's the answer I came up with. The "connector scripts" which are user-customizable extensions, use SSH's -o option to "turn off" strict host checking (which avoids the user having to acknowledge/accept a new known_hosts entry for each connection) and to specify a temporary known_hosts file which is deleted after the script is run (which avoids the endless bloat of the known_hosts file). The drawback is that this bypasses SSH's host-checking algorithm, which is also a means to avoid spoofing-style attacks. So there is a trade-off at play here.

Finally, with some help from SteveL, I managed to crack the stability issue when malformed packets or packets with the wrong password come in to the hydrophone script. Previously, this would crash the entire script, essentially creating a DoS.
Around line 81, I changed the payload= assignment statement to the following:

Now, if a bad packet comes in an error is logged and the script goes back to listening. I've tested it pretty thoroughly here.
I'd post a diff, but I've lost my original copy of hydrophone, and there isn't a complete copy posted anywhere in this thread that patches cleanly for me._________________

aidanjt wrote:

You see, instead of arguing from ignorance, and fear, there is only one way to verify a theory. And that's not by clutching a black book and begging the sky fairy for deliverance from the mad scientists and their big machines.

Awesome! I have not been having any problems, but that may be circumstantial.

Regarding this code: I like the use of PIPESTATUS (a builtin I didn't know about) as an alternative to breaking out the statements (so we can check the return code of the openssl enc statement. I'd like to use that. The alternative is breaking up that series of pipes and checking exit status of the openssl command (and 'return' if it's bad, to abort the f_decompose function).

However, other than the use of PIPESTATUS (and possibly printf) this function seems to introduce some inefficiency. Both of those can be used inline without nesting an additional function. Theoretically at least, the additional function call introduces overhead, as does its argument-handling. I don't follow the need to check the argument count, since the function call is intrinsic to the script (there will always be the right number of args). I'm not sure why printf is preferable to echo, but that could be substituted in the code as it stands without a function. I realize the rationale here may be over my head, so don't hesitate to educate me.

Once we decide on this, why don't you make whatever changes are called for (if any) to your copy and post it here (since you've been actively working on it). I'll copy that and we'll be on the same page.

It actually leaves the -w option unusable. But I guess you just forgot to add support for WOL and left it that way to be modified sometime in the future...

Yes, that's right. They are just place-holders. I didn't have a need for it, but I wanted to make it clear that you could use a single packet as an event trigger for basically anything (sleep was one thing that came to mind, although you could have it toggle a service on or off, run a job, percolate your coffee, trigger the Self-Destruct Sequence, etc.).

I'm still running this, by the way. It's a hack, but it works.

[Edit: I didn't include script for WOL, but it's trivial. Emerge the package and then do something like: