Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
Single-Packet Authentication (Crypto-Port-Knocking) in BASH
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index Documentation, Tips & Tricks
View previous topic :: View next topic  
Author Message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Sun Apr 13, 2008 10:29 am    Post subject: Single-Packet Authentication (Crypto-Port-Knocking) in BASH Reply with quote

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:
Code:
# ~/.ramius/ramius.conf

# This is the user configuration file for ramius port-knocking script

# your Linux username on target host (the machine you want to log onto)
LOGIN="bonekracker"

# the address of the firewall
FW="bone.yard.com"

# location of the hping binary (or a symlink to it) on this machine:
HPING="/usr/sbin/hping"

# temporary file for outbound packet
TMP="/var/tmp/.ramius_payload"

## the following configurables must match the firewall's settings

# encryption passphrase clause (See OPENSSL(1): PASS PHRASE ARGUMENTS):
PAS="pass:Tr1bbl3s!E@t!Qu@dr0tr1tic@le"
#PAS="file:~/.ramius_kfile"

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



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".
Code:

#!/bin/bash

# ~/bin/ramius
# BoneKracker
# Rev. 13 April 2008

# Purpose:
# Cryptographic port-knocking client (single-packet authentication)
# for secure connection via the hydrophone port-knocking daemon.

# Note: Run using sudo. See USAGE below for command-line options.

# Installation.  See README.txt

#-----------------------------------------------------------------------------

##### CONFIGURATION

# identify real user and source their configuration file

USER=$(ps -p $PPID -o ruser=)
source /home/${USER}/.ramius/ramius.conf


##### USAGE FUNCTION

f_Usage() {
cat <<EOF
 == USAGE ====================================================================
|  ramius [-[s|r][h|f|w][c|n|x]]                                              |
|                                                                             |
|  This script accepts up to thee single-letter options preceded by a         |
|  single hyphen (such as "ramius" or "ramius -rfn" ).                        |
|                                                                             |
|  You may indicate up to one TARGET option (default = s)                     |
|  -s   "server"                                                              |
|  -r   "router"                                                              |
|                                                                             |
|  You may indicate up to one SERVICE option (default = h)                    |
|  -h   "ssh"         : open firewall port to target for one ssh session      |
|  -f   "ftp"         : open firewall port to target for one ftp session      |
|  -w   "wake-target" : ask firewall to send Wake-On-LAN packet to target     |
|                                                                             |
|  You may indicate up to one UI option (default = c)                         |
|  -c   "cli"         : auto-start service in the shell (command-line)        |
|  -n   "nautilus"    : auto-start service in nautilus (for ssh or ftp)       |
|  -x   "noauto"      : do not auto-start a service (you will do it manually) |
|                                                                             |
 =============================================================================
EOF
exit $E_PARAMS
}

##### EXIT CODES:
E_PARAMS=3              # invalid or wrong number of arguments
E_CRYPT=4               # openssl encryption of the message failed
E_NET=5                 # hping of the firewall failed


##### OPTION PROCESSING:

while getopts ":srhfwcnx" OPTION; do
        case $OPTION in
                s ) [ -z "$TGT" ] && TGT="s" || f_Usage;;
                r ) [ -z "$TGT" ] && TGT="r" || f_Usage;;
                h ) [ -z "$SVC" ] && SVC="h" || f_Usage;;
                f ) [ -z "$SVC" ] && SVC="f" || f_Usage;;
                w ) [ -z "$SVC" ] && SVC="w" || f_Usage;;
                c ) [ -z "$UI" ]  && UI="c"  || f_Usage;;
                n ) [ -z "$UI" ]  && UI="n"  || f_Usage;;
                x ) [ -z "$UI" ]  && UI="x"  || f_Usage;;
                * ) f_Usage;;
        esac
done
shift $(($OPTIND - 1))

# assign default options
TGT=${TGT:="s"} # server
SVC=${SVC:="h"} # ssh
UI=${UI:="c"}   # shell

## catch invalid option combinations

[ "$TGT" == "r" ] && [ "$SVC" == "w" ] && echo "Router is always awake." && exit 0
[ "$TGT" == "s" ] && [ "$SVC" == "w" ] && echo "Server does not currently sleep." && exit 0


##### MAIN LOGIC

## select random ports for the knock and the service connection

# get local "local port range"
LOC_LO=$(awk {'print $1'} /proc/sys/net/ipv4/ip_local_port_range)
LOC_HI=$(awk {'print $2'} /proc/sys/net/ipv4/ip_local_port_range)

# use only ports within local port range of both machines
[ "$LOC_LO" -gt "$FW_LO" ] && U_LO=$LOC_LO || U_LO=$FW_LO
[ "$LOC_HI" -lt "$FW_HI" ] && U_HI=$LOC_HI || U_HI=$FW_HI

# in that resulting set, select a pseudo-random port on which to knock
(( PING_PORT = $RANDOM % (U_HI - U_LO) + U_LO ))

# and select a pseudo-random port for the service connection
(( SVC_PORT = $RANDOM % (U_HI - U_LO) + U_LO ))

## generate 16 random bytes to make this packet extra-unique
NONCE=$(openssl rand -base64 12)

## generate timestamp
TSTAMP=$(date -u +%s)

## concatenate those elements of the message
MSG="${NONCE}:${TSTAMP}:${TGT}:${SVC}:${SVC_PORT}"

## append a cryptographic hash of the message
MSG="${MSG}:$(echo $MSG | openssl md5)"

## encrypt that payload string and temporarily save it
echo $MSG | openssl enc -aes256 -salt -pass $PAS -e -a -A > $TMP || exit $E_CRYPT

## get payload size (in bytes)
SIZE=$(stat -c%s "$TMP")

# send message in a single innocuous ping (a la Hunt for Red October)
echo "Give me a ping, Vasili... one ping only, please."
$HPING -1 -c 1 -y -P -q -d $SIZE -p $PING_PORT -E $TMP $FW &>/dev/null

## clean up
rm $TMP

#### Initate service connection interfaces (with user privileges):

# real user's ramius_agent script
USER_AGENT="/home/${USER}/.ramius/ramius_agent"

# call user agent to initiate the connection via chosen interface
if [ -x $USER_AGENT ]; then
        su ${USER} -c "$USER_AGENT $FW $LOGIN $SVC $SVC_PORT $UI"
else
        echo "Unable to execute ramius_agent script." && exit $E_PARAMS
fi

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):
Code:
#!/bin/bash

# ~/.ramius/ramius_agent
# BoneKracker
# Rev. 13 April 2008

# Purpose: extend ramius port-knocking client with user-privileged
# functionality.  Once the root-privileged ramius script has opened a
# firewall port via single-packet authentication, it calls this script
# as the real user to initiate the actual service connection (e.g. ssh,
# ftp, etc.) with user privileges and via the user's chosen interface
# (e.g., shell, nautilus, etc.).

# This file should be in the ~/.ramius directory and executable by the user.

# receive parameters from ramius script
FW=$1
LOGIN=$2
SVC=$3
SVC_PORT=$4
UI=$5

# used to keep redundant host entries from cluttering ~/.ssh/known_hosts
[ -w ~/.ramius/known_hosts.tmp ] && echo "" > ~/.ramius/known_hosts.tmp

## initiate the selected connection (or prompt user to do so)
case $UI in
        c )
                case $SVC in
                        h ) ssh -p ${SVC_PORT} -l ${LOGIN} ${FW} 2>/dev/null;;
                        f ) ftp -p ${FW}:${SVC_PORT};;
                esac
        ;;
        n )
                case $SVC in
                        h ) nautilus ssh://${LOGIN}@${FW}:${SVC_PORT}&;;
                        f ) nautilus ftp://${FW}:${SVC_PORT}&;;
               esac
        ;;
        x )     echo "Okay, manually connect now to ${FIREWALL} on port ${SVC_PORT}."
        ;;
esac

exit 0



On the server side: this is the daemon script, which I have located in /root/bin but should probably be in /usr/local/bin or someplace like that:
Code:
#!/bin/bash

# hydrophone
# BoneKracker
# Rev. 8 May 2008

# Purpose:
# A tcpdump wrapper that serves as a cryptographic port-knocking daemon
# (i.e., "single-packet authentication"). Uses the "airlock" extension script.

##### CONFIGURATION

# interface on which to listen (external, probably)
IFACE="eth0"

# maximum acceptable age of incoming packet (seconds)
AGE_LIMIT="10"

# how long opened ports should remain open (seconds)
WAIT="20"

# encryption passphrase clause (re: OPENSSL(1): PASS PHRASE ARGUMENTS)
#PASSARG="file:/root/.hydrophone_key"
PASSARG='pass:Tr1bbl3s!E@t!Qu@dr0tr1tic@le'

# location of action scripts
AIRLOCK="/root/bin/hydrophone/airlock"
WAKER="/root/bin/hydrophone/wake"

# temporary file used as buffer for incoming packets
BUFFER="/var/tmp/hydrophone/packet_buffer"

# directory to contain persistent variable data files
DATA="/var/lib/hydrophone/"

# compare the last <HIST_SIZE> packets to new ones (to prevent replay attack)
HIST_SIZE="1500"

# pid file to create when started
PIDFILE="/var/run/hydrophone.pid"

# tcpdump expression used to discriminate SPA packets from noise
FILTER='icmp[icmptype] = icmp-echo and (greater 128 and less 576)'

##### EXIT CODES:
E_PARAMS=3              # invalid or wrong number of arguments
E_EXEC=4                # not executable (e.g., permission, not installed)
E_NET=5                 # tcpdump packet capture failed
E_CRYPT=6               # decryption of the payload failed

##### INITIALIZATION:

# verify tools are accessible (and register full paths with shell)
TOOLS="iptables logger openssl tcpdump"
for TOOL in $TOOLS; do
        hash $TOOL || exit $E_EXEC
done

# create directories if missing
[ -d ${BUFFER%/*} ] || mkdir -pm 0770 ${BUFFER%/*}
[ -d ${DATA%/*} ] || mkdir -pm 0770 ${DATA%/*}

# advertise the hydrophone service's running status
echo $$ > $PIDFILE


##### Packet Decomposition Function:

f_decompose() {

## extract tcpdump capture time and source IP address

TIME_CAPTURED=$(sed -n -e '1s/\..*//p' $BUFFER)
IP=$(cut -d " " -s -f 3 $BUFFER)

## extract, decrypt and parse the packet's data into an array

payload=( $(sed -n -r -e '1d' -e 's/.+\.+//g' -e $'p' $BUFFER \
        | openssl enc -aes256 -salt -pass $PASSARG -d -a -A \
        | xargs -d :) ) || exit $E_CRYPT

# assign the array elements to their respective variables,

FIELDS="TIMESTAMP TARGET SERVICE PORT HASH"
i=1     # ignore the Nonce, ${payload[0]}

for FIELD in $FIELDS; do
        eval $FIELD=${payload[i++]}
done

## verify message is timely

AGE=$(($TIME_CAPTURED - $TIMESTAMP))
[ "${AGE}" -lt "0" ] && AGE=$((0 - $AGE))

if [ "${AGE}" -gt "${AGE_LIMIT}" ]; then
        AGE_STATUS="POSSIBLE REPLAY!"
        REJECT="Y"
else
        AGE_STATUS="okay"
        REJECT="N"
fi

## verify message is original

if [ grep $HASH ${DATA}/history.* &>/dev/null ]; then
        HASH_STATUS="REPLAY!"
        REJECT="Y"
else
        HASH_STATUS="okay"
        echo $HASH >> ${DATA}/history.0
        REJECT="N"
fi


## If the packet is acceptable, dispatch a subshell to take the
## appropriate action while the main process returns to listening.
## stdout/stderr must be redirected or the main process will wait.

if [ "$REJECT" = "N" ]; then
        echo "**Packet Accepted.**"
        case $SERVICE in
                "h" | "f" ) $AIRLOCK $TARGET $IP $SERVICE $PORT $WAIT 2>&1 | logger -t hydrophone: & ;;
                "w"       ) $WAKER $TARGET 2>&1 | logger -t hydrophone: & ;;
                *         ) echo "Invalid Service: ${SERVICE}" && exit $E_PARAMS;;
        esac
else

        echo "**PACKET REJECTED!**"
fi


## announce (log) the packet

echo "Source:    ${IP}
  Timestamp: ${TIMESTAMP}
  Target:    ${TARGET}
  Service:   ${SERVICE}
  Port:      ${PORT}
  Age:       ${AGE} (${AGE_STATUS})
  Hash:      ${HASH} (${HASH_STATUS})"

       
## rotate packet history data files if necessary

if [ "$(wc -l < ${DATA}/history.0)" -gt "$((${HIST_SIZE}/3))" ]; then
        [ -f ${DATA}/history.1 ] && mv ${DATA}/history.1 ${DATA}/history.2
        mv ${DATA}/history.0 ${DATA}/history.1
fi

}

##### Main Loop (Listen & Packet Capture)

while true; do
        tcpdump -c 1 -AttnnNp -i ${IFACE} -s 0 ${FILTER} > ${BUFFER} || exit $E_NET
        f_decompose
done

exit 0


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

airlock-event() {

    # RULENUM: we want to insert *after* "related,established accept"; and we
    # don't want "${RULE}" in the delete command).
    [ $ACTION == "A" ] && RULE=" 2" || RULE=""

    case $TARGET in
        r ) # Target: Router (REDIRECT)
            echo "Airlock Event: Router (REDIRECT) ->${ACTION})"
            /sbin/iptables -t nat -${ACTION} net_dnat -s ${IP} -p tcp -m tcp --dport ${PORT} -j LOG --log-prefix "Hydrophone:net_dnat:REDIRECT:" --log-level 6
            /sbin/iptables -t nat -${ACTION} net_dnat -s ${IP} -p tcp -m tcp --dport ${PORT} -j REDIRECT --to-ports ${INT_PORT}
            /sbin/iptables -t filter -${!ACTION} net2fw ${RULE} -s ${IP} -p tcp -m tcp --dport ${INT_PORT} -j ACCEPT
            ;;
        s ) # Target: Server (DNAT)
            echo "Airlock Event: Server (DNAT) ->${ACTION})"
            /sbin/iptables -t nat -${ACTION} net_dnat -s ${IP} -p tcp -m tcp --dport ${PORT} -j LOG --log-prefix "Hydrophone:net_dnat:DNAT:" --log-level 6
            /sbin/iptables -t nat -${ACTION} net_dnat -s ${IP} -p tcp -m tcp --dport ${PORT} -j DNAT --to-destination ${SERVER_IP}:${INT_PORT}
            /sbin/iptables -t filter -${!ACTION} net2dmz ${RULE} -s ${IP} -d ${SERVER_IP}/32 -p tcp -m tcp --dport ${INT_PORT} -j ACCEPT
            ;;
    esac

}

##### main logic
ACTION="A"
airlock-event
sleep $HOLD
ACTION="D"
airlock-event
exit 0


And from the README file that I use with the client, this explains the use of an entry in ~/.ssh/config to avoid having the use of random ports for ssh connections fill up your known_hosts file:
Code:
Installation:

 1. Save this folder (ramius) in your home directory as .ramius/

 2. Set ownership and permissions on the "ramius" file:
      'cd ~/.ramius'
      'sudo chown root:root ramius'
      'sudo chmod 0750 ramius'

 3. Set (verify) ownership and permissions on the "ramius_agent" file:
      'chown <you>:<you> ramius_agent'Installation:

 4. Put ramius file somewhere on root's path accessible to users (this
    is optional, you can put it wherever you want).
       'sudo mv ramius /usr/local/bin'

 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.

   Host <the.firewall.com>
      CheckHostIP no
      StrictHostKeyChecking no
      UserKnownHostsFile ~/.ramius/known_hosts.tmp

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":
Code:
#!/sbin/runscript

depend() {
        need firewall
}


start() {
        ebegin "Starting ${SVCNAME}"
        /root/bin/hydrophone/hydrophone 2>&1 | logger -t hydrophone: &
        eend $?
}

stop() {
        ebegin "Stopping ${SVCNAME}"
        kill $(pgrep hydrophone)
        kill $(pgrep tcpdump)
        [ -f /var/run/hydrophone.pid ] && rm /var/run/hydrophone.pid
        eend $?
}


Last edited by Bones McCracker on Tue Jun 03, 2008 5:45 am; edited 12 times in total
Back to top
View user's profile Send private message
UberPinguin
Guru
Guru


Joined: 20 Nov 2005
Posts: 510
Location: 2416.94 Miles From Home

PostPosted: Fri May 02, 2008 4:19 pm    Post subject: Reply with quote

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:
ramius:
ramius_scripting/ramius-orig: line 97: [: : integer expression expected
ramius_scripting/ramius-orig: line 98: [: : integer expression expected
ramius_scripting/ramius-orig: line 101: ((: PING_PORT = 17509 % (U_HI - U_LO) + U_LO : division by 0 (error token is "+ U_LO ")
ramius_scripting/ramius-orig: line 104: ((: SVC_PORT = 18889 % (U_HI - U_LO) + U_LO : division by 0 (error token is "+ U_LO ")
Give me a ping, Vasili... one ping only, please.
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.
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Mon May 05, 2008 6:34 am    Post subject: Reply with quote

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". :P
Back to top
View user's profile Send private message
UberPinguin
Guru
Guru


Joined: 20 Nov 2005
Posts: 510
Location: 2416.94 Miles From Home

PostPosted: Mon May 05, 2008 11:53 am    Post subject: Reply with quote

BoneKracker wrote:
UberPinguin wrote:
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.
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". :P
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,
Code:
[ -d ${BUFFER%/*} ] || mkdir -pm 0770 ${BUFFER%/*}
[ -d ${HISTORY%/*} ] || mkdir -pm 0770 ${HISTORY%/*}
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
Code:
[ -f "$BUFFER" ] || touch "$BUFFER"
[ -f "$HISTORY" ] || touch "$HISTORY"
and everything worked like a charm.

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.
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Tue May 06, 2008 12:11 am    Post subject: Reply with quote

No, -a (spoof) was never one of the flags I used.

What this does is simply check to see if the directories exist, and if they don't, creates them. It shouldn't have caused an error:
Code:
[ -d ${BUFFER%/*} ] || mkdir -pm 0770 ${BUFFER%/*}
[ -d ${HISTORY%/*} ] || mkdir -pm 0770 ${HISTORY%/*}

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:
Code:
[ -d $(dirname ${BUFFER}) ] || mkdir -pm 0770 $(dirname ${BUFFER})
[ -d $(dirname ${HISTORY}) ] || mkdir -pm 0770 $(dirname ${HISTORY})


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
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Tue May 06, 2008 3:14 am    Post subject: Reply with quote

Regarding the intermittent failure:

The vast majority of the time, the captured packet text looks like this:
Code:

1210045667.704504 IP 192.168.2.101 > 192.168.2.100: ICMP echo request, id 64793, seq 0, length 138
E.....@.@..W...e...d...,....U2FsdGVkX18JnCaBWsjoBeAtUvuSZJSK47rDR3YcMa8J4xk+97hhKweXufQml5ni
NTgKeujS09+gpJH+F0qzyWKvC7LIg/8KHs3PJLl/XD8y81luJ061atXoCADJP38Z


1210045731.829688 IP 192.168.2.101 > 192.168.2.100: ICMP echo request, id 3866, seq 0, length 138
E...j.@.@.I....e...d..P5....U2FsdGVkX19F9iX9183AOIQn7Jz++PrGDaMZJKf3LGPTVPR8mUCBxeErFlm+V8o2
cEnG28O8lACUJDFiUUnm2ZpZ16W9jtSbowgfo0cVUNcO1LxFJcZDibHV2SrfmZqI


1210045744.825528 IP 192.168.2.101 > 192.168.2.100: ICMP echo request, id 8474, seq 0, length 138
E....n@.@.)....e...d....!...U2FsdGVkX1+xFGMA+jF7gVKfwBZ76MvIaXpwomQgmKWItWostS9SqHUM5Fgakt7k
L+QaW3DGYNTEmISY7tfo8/A0Qlw3kw4tV3elnlkURcFauZGqxoOiEH+tOsOA3XRA

The encrypted message string is everything after the last dots. In the final example above, the encrypted message string is:
Code:

U2FsdGVkX1+xFGMA+jF7gVKfwBZ76MvIaXpwomQgmKWItWostS9SqHUM5Fgakt7k
L+QaW3DGYNTEmISY7tfo8/A0Qlw3kw4tV3elnlkURcFauZGqxoOiEH+tOsOA3XRA

We need the program to isolate the encrypted message string from the rest of the packet text. The way I have been doing that is this:
Code:

sed -n -r -e '2s/.+\.+//gp' -e '3p'
That says, "on line 2, erase all occurrences of characters followed by dots and keep the rest along with line 3.

As you can see, that would work fine in the cases above.

Here is the problem. For some reason I do not understand, tcpdump occasionally outputs the packet text looking like this:
Code:
1210041305.322932 IP 192.168.2.101 > 192.168.2.100: ICMP echo request, id 46360, seq 0, length 138
E....;@.@..
...e...d........U2FsdGVkX18X50k8jc2yXezgPxZb2EulAws8tjnE5qAZpgrC+IYH6Y70cArjtesA
rKByU085UQbCKmkDygErEK56N/j3AXukSAAD0NC2IL8a/gAQTGCNC3iED4rTbmX8
Note the extra line break.

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'):
Code:
--- hydrophone   2008-05-05 23:32:31.927239604 -0400
+++ hydrophone.new   2008-05-06 02:19:28.247668328 -0400
@@ -72,9 +72,9 @@
 
 ## extract, decrypt and parse the packet's data into an array
 
-payload=( $(sed -n -r -e '2s/.+\.+//gp' -e '3p' $BUFFER \
-   | openssl enc -aes256 -salt -pass $PASSARG -d -a \
-   | xargs -d :) ) || exit $E_CRYPT
+payload=( $(sed -n -r -e '1d' -e $!'s/.+\.+//g' -e $'p' $BUFFER \
+        | openssl enc -aes256 -salt -pass $PASSARG -d -a \
+        | xargs -d :) ) || exit $E_CRYPT
 
 # assign the array elements to their respective variables,

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:
Code:
1210057088.498493 IP 192.168.2.101 > 192.168.2.100: ICMP echo request, id 14623, seq 0, length 138
E...<
@.@.x;...e...d...N9...U2FsdGVkX1+igjS04BGx49NHesKCoZFjI98KSeX64gFZyDqTK2hGzZjbLOFSLr3r
HwErGCU2/Wwbvq2CkM7qB0XGo2GmwSvbmbHDWIie9lops/fXAAGZp6xjYlZXXp9M


I'm tempted to just extract it based on character position or the encryption string prefix.

More tweaking required. :?
Back to top
View user's profile Send private message
UberPinguin
Guru
Guru


Joined: 20 Nov 2005
Posts: 510
Location: 2416.94 Miles From Home

PostPosted: Tue May 06, 2008 3:37 pm    Post subject: Reply with quote

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.
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Thu May 08, 2008 1:44 am    Post subject: Reply with quote

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.
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Thu May 08, 2008 4:30 am    Post subject: Reply with quote

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:
Code:
--- hydrophone.old   2008-05-07 22:17:05.553928684 -0400
+++ hydrophone   2008-05-08 00:18:22.169929563 -0400
@@ -2,7 +2,7 @@
 
 # hydrophone
 # BoneKracker
-# Rev. 26 March 2008
+# Rev. 8 May 2008
 
 # Purpose:
 # A tcpdump wrapper that serves as a cryptographic port-knocking daemon
@@ -21,7 +21,7 @@
 
 # encryption passphrase clause (re: OPENSSL(1): PASS PHRASE ARGUMENTS)
 #PASSARG="file:/root/.hydrophone_key"
-PASSARG="pass:Tr1bbl3s!E@t!Qu@dr0tr1tic@le"
+PASSARG='pass:Tr1bbl3s!E@t!Qu@dr0tr1tic@le'
 
 # location of action scripts
 AIRLOCK="/root/bin/hydrophone/airlock"
@@ -74,8 +74,8 @@
 
 ## extract, decrypt and parse the packet's data into an array
 
-payload=( $(sed -n -r -e '1d' -e $!'s/.+\.+//g' -e $'p' $BUFFER \
-        | openssl enc -aes256 -salt -pass $PASSARG -d -a \
+payload=( $(sed -n -r -e '1d' -e 's/.+\.+//g' -e $'p' $BUFFER \
+        | openssl enc -aes256 -salt -pass $PASSARG -d -a -A \
         | xargs -d :) ) || exit $E_CRYPT
 
 # assign the array elements to their respective variables,
Back to top
View user's profile Send private message
UberPinguin
Guru
Guru


Joined: 20 Nov 2005
Posts: 510
Location: 2416.94 Miles From Home

PostPosted: Thu May 08, 2008 11:49 pm    Post subject: Reply with quote

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.
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Thu May 08, 2008 11:53 pm    Post subject: Reply with quote

Did you add the '-A' option to both sides (client and server)? It needs to go in the 'ssl enc' command in both the hydrophone script and the ramius script.

I suppose I probably should have mentioned that. :?
Back to top
View user's profile Send private message
UberPinguin
Guru
Guru


Joined: 20 Nov 2005
Posts: 510
Location: 2416.94 Miles From Home

PostPosted: Fri May 09, 2008 12:02 am    Post subject: Reply with quote

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.
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Fri May 09, 2008 3:29 am    Post subject: Reply with quote

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:
Code:
--- hydrophone.20080507   2008-05-08 22:11:26.331893329 -0400
+++ hydrophone   2008-05-08 23:06:01.480887153 -0400
@@ -27,11 +27,14 @@
 AIRLOCK="/root/bin/hydrophone/airlock"
 WAKER="/root/bin/hydrophone/wake"
 
-# location for temporary file
+# temporary file used as buffer for incoming packets
 BUFFER="/var/tmp/hydrophone/packet_buffer"
 
-# location for packet history file (suggest rotation at 1 MiB +)
-HISTORY="/var/lib/hydrophone/packet_history"
+# directory to contain persistent variable data files
+DATA="/var/lib/hydrophone/"
+
+# compare the last <HIST_SIZE> packets to new ones (to prevent replay attack)
+HIST_SIZE="1500"
 
 # pid file to create when started
 PIDFILE="/var/run/hydrophone.pid"
@@ -55,7 +58,7 @@
 
 # create directories if missing
 [ -d ${BUFFER%/*} ] || mkdir -pm 0770 ${BUFFER%/*}
-[ -d ${HISTORY%/*} ] || mkdir -pm 0770 ${HISTORY%/*}
+[ -d ${DATA%/*} ] || mkdir -pm 0770 ${DATA%/*}
 
 # advertise the hydrophone service's running status
 echo $$ > $PIDFILE
@@ -100,25 +103,15 @@
 
 ## verify message is original
 
-if [ grep $HASH $HISTORY &>/dev/null ]; then
+if [ grep $HASH ${DATA}/history.* &>/dev/null ]; then
    HASH_STATUS="REPLAY!"
    REJECT="Y"
 else
    HASH_STATUS="okay"
-   echo $HASH >> $HISTORY
+   echo $HASH >> ${DATA}/history.0
    REJECT="N"
 fi
 
-## announce (log) the packet
-
-echo "Source:    ${IP}
-  Timestamp: ${TIMESTAMP}
-  Target:    ${TARGET}
-  Service:   ${SERVICE}
-  Port:      ${PORT}
-  Age:       ${AGE} (${AGE_STATUS})
-  Hash:      ${HASH} (${HASH_STATUS})"
-   
 
 ## If the packet is acceptable, dispatch a subshell to take the
 ## appropriate action while the main process returns to listening.
@@ -135,6 +128,25 @@
    echo "**PACKET REJECTED!**"
 fi
 
+
+## announce (log) the packet
+
+echo "Source:    ${IP}
+  Timestamp: ${TIMESTAMP}
+  Target:    ${TARGET}
+  Service:   ${SERVICE}
+  Port:      ${PORT}
+  Age:       ${AGE} (${AGE_STATUS})
+  Hash:      ${HASH} (${HASH_STATUS})"
+
+   
+## rotate packet history data files if necessary
+
+if [ "$(wc -l < ${DATA}/history.0)" -gt "$((${HIST_SIZE}/3))" ]; then
+   [ -f ${DATA}/history.1 ] && mv ${DATA}/history.1 ${DATA}/history.2
+   mv ${DATA}/history.0 ${DATA}/history.1
+fi
+
 }
 
 ##### Main Loop (Listen & Packet Capture)
Back to top
View user's profile Send private message
UberPinguin
Guru
Guru


Joined: 20 Nov 2005
Posts: 510
Location: 2416.94 Miles From Home

PostPosted: Sat May 10, 2008 10:15 pm    Post subject: Reply with quote

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:
Code:
## extract, decrypt and parse the packet's data into an array
data=$(<"$BUFFER")
payload=( $(echo ${data##*.} | openssl enc -aes256 -salt -pass $PASSARG -d -a -A | xargs -d :) )|| exit $E_CRYPT
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:
/etc/init.d/hydrophone:
#!/sbin/runscript

depend() {
        need iptables
}


start() {
        ebegin "Starting ${SVCNAME}"
        start-stop-daemon --start --quiet --exec /usr/local/sbin/hydrophone 2>&1 | logger -t hydrophone &
        eend $?
}

stop() {
        ebegin "Stopping ${SVCNAME}"
        start-stop-daemon --stop --quiet --exec /usr/local/sbin/hydrophone
        if  pgrep -fx "logger -t hydrophone" >/dev/null; then kill -s 9 $(pgrep -fx "logger -t hydrophone"); fi
        eend $?
}

_________________
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.
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Sat May 10, 2008 11:59 pm    Post subject: Reply with quote

UberPinguin wrote:
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:
Code:
## extract, decrypt and parse the packet's data into an array
data=$(<"$BUFFER")
payload=( $(echo ${data##*.} | openssl enc -aes256 -salt -pass $PASSARG -d -a -A | xargs -d :) )|| exit $E_CRYPT
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:
Code:

## extract, decrypt and parse the packet's data into an array

# read the tcpdump file
PACKET=$(<"$BUFFER")

#extract the payload text and decrypt it (on failure return to listening and log bad packet)
PAYLOAD= $(echo "${PACKET##*.}" | openssl enc -aes256 -salt -pass $PASSARG -d -a -A)

if [ "${$?}" -gt "0" ]; then
     echo "Bad Packet!!"
     echo $PACKET
     return $E_CRYPT
fi

#parse the payload (a colon-delimited string) into an array of values
values=( $( echo "${PAYLOAD} | xargs -d :) )

# assign the values to their respective variables,
FIELDS="TIMESTAMP TARGET SERVICE PORT HASH"
i=1     # ignore the Nonce, ${payload[0]}

for FIELD in $FIELDS; do
        eval $FIELD=${values[i++]}
done

## verify message is timely


UberPinguin wrote:
[EDIT]I also have a modified version of /etc/init.d/hydrophone:
/etc/init.d/hydrophone:
#!/sbin/runscript

depend() {
        need iptables
}


start() {
        ebegin "Starting ${SVCNAME}"
        start-stop-daemon --start --quiet --exec /usr/local/sbin/hydrophone 2>&1 | logger -t hydrophone &
        eend $?
}

stop() {
        ebegin "Stopping ${SVCNAME}"
        start-stop-daemon --stop --quiet --exec /usr/local/sbin/hydrophone
        if  pgrep -fx "logger -t hydrophone" >/dev/null; then kill -s 9 $(pgrep -fx "logger -t hydrophone"); fi
        eend $?
}

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?
Back to top
View user's profile Send private message
avx
Advocate
Advocate


Joined: 21 Jun 2004
Posts: 2152

PostPosted: Thu May 15, 2008 3:37 pm    Post subject: Reply with quote

Nice one, altough it has some bash-ish - yeah, I know, bash is a dep on gentoo, but I still avoid it whenever possible. I'm trying to get this to perl if you don't mind. Till then, thanks.
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Fri May 16, 2008 4:00 am    Post subject: Reply with quote

ph030 wrote:
Nice one, altough it has some bash-ish - yeah, I know, bash is a dep on gentoo, but I still avoid it whenever possible. I'm trying to get this to perl if you don't mind. Till then, thanks.


Heck no, I don't mind. :)

Like I've said, this was a proof of concept, to demonstrate that people can create their own home-grown SPA solutions.

I'll be very interested to see what you come up with, which I hope you'll share.
Back to top
View user's profile Send private message
michaelrash
n00b
n00b


Joined: 03 Jun 2008
Posts: 2

PostPosted: Tue Jun 03, 2008 2:03 am    Post subject: Single Packet Authorization with Port Randomization Reply with quote

Hi -

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:

http://www.cipherdyne.org/blog/2008/06/single-packet-authorization-with-port-randomization.html

Thanks,
_________________
Michael Rash
http://www.cipherdyne.org/
Key fingerprint = 53EA 13EA 472E 3771 894F AC69 95D8 5D6B A742 839F
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Tue Jun 03, 2008 4:22 am    Post subject: Re: Single Packet Authorization with Port Randomization Reply with quote

michaelrash wrote:
Hi -

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:

http://www.cipherdyne.org/blog/2008/06/single-packet-authorization-with-port-randomization.html

Thanks,


I'm flattered. :)

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).
Back to top
View user's profile Send private message
michaelrash
n00b
n00b


Joined: 03 Jun 2008
Posts: 2

PostPosted: Wed Jun 04, 2008 3:10 am    Post subject: Re: Single Packet Authorization with Port Randomization Reply with quote

BoneKracker wrote:


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:

http://trac.cipherdyne.org/trac/fwknop/browser/fwknop/trunk/patches/openssh-4.3p2_SPA.patch?rev=1114
_________________
Michael Rash
http://www.cipherdyne.org/
Key fingerprint = 53EA 13EA 472E 3771 894F AC69 95D8 5D6B A742 839F
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Wed Jun 04, 2008 4:43 am    Post subject: Re: Single Packet Authorization with Port Randomization Reply with quote

michaelrash wrote:
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:

http://trac.cipherdyne.org/trac/fwknop/browser/fwknop/trunk/patches/openssh-4.3p2_SPA.patch?rev=1114


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.
Back to top
View user's profile Send private message
UberPinguin
Guru
Guru


Joined: 20 Nov 2005
Posts: 510
Location: 2416.94 Miles From Home

PostPosted: Thu Jul 24, 2008 3:05 am    Post subject: Reply with quote

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:
Code:
pingCrypt() { (($#>1)) || { echo -E "Bad use of $FUNCNAME">&2; exit 1;}; local pass=$1; shift; printf '%s\n' "$@" | openssl enc -aes256 -salt -pass "$pass" -d -a -A | xargs -d :; return "${PIPESTATUS[1]}"; }
if payload=( $(pingCrypt "$PASSARG" "${data##*.}") ); then
The rest of f_decompose() proceeds as before, ending with:
Code:
else
        echo "Bad packet. Exit code $E_CRYPT"
fi
}
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.
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Thu Jul 24, 2008 5:03 am    Post subject: Reply with quote

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. :)

Maybe it would be more efficient to simply use PIPESTATUS like this:
Code:
data=$(<"$BUFFER")
payload=( $(echo ${data##*.} | openssl enc -aes256 -salt -pass $PASSARG -d -a -A | xargs -d :) )
[ ${PIPESTATUS[1]} -gt 0 ] && return 1
  .
  .
  .
##### Main Loop (Listen & Packet Capture)

while true; do
        tcpdump -c 1 -AttnnNp -i ${IFACE} -s 0 ${FILTER} > ${BUFFER} || exit $E_NET
        f_decompose || echo "Packet Decryption Failed" | logger -t hydrophone
done


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. :)
Back to top
View user's profile Send private message
.yankee
Apprentice
Apprentice


Joined: 24 Feb 2008
Posts: 194
Location: Polska

PostPosted: Wed Apr 08, 2009 10:37 pm    Post subject: Reply with quote

Seems like a great set of scripts, BoneKracker!
Though this makes little sense:
Code:

[ "$TGT" == "r" ] && [ "$SVC" == "w" ] && echo "Router is always awake." && exit 0
[ "$TGT" == "s" ] && [ "$SVC" == "w" ] && echo "Server does not currently sleep." && exit 0

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...
Back to top
View user's profile Send private message
Bones McCracker
Veteran
Veteran


Joined: 14 Mar 2006
Posts: 1611
Location: U.S.A.

PostPosted: Wed Apr 08, 2009 11:21 pm    Post subject: Reply with quote

.yankee wrote:
Seems like a great set of scripts, BoneKracker!
Though this makes little sense:
Code:

[ "$TGT" == "r" ] && [ "$SVC" == "w" ] && echo "Router is always awake." && exit 0
[ "$TGT" == "s" ] && [ "$SVC" == "w" ] && echo "Server does not currently sleep." && exit 0

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:
Code:

network_address="X.X.X.255"
mac_address="XX:XX:XX:XX:XX:XX"
wakeonlan -i $network_address $mac_address  1>/dev/null
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Documentation, Tips & Tricks All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum