2012-02-10

Bash script for simple port-knocking in iptables

This is a simple bash script that allows you to add (multiple) port-knocking rules into iptables. Usage is explained below.
KNOCK_NAME=$1

PORT_1=$2
PORT_2=$3
PORT_3=$4
PORT_4=$5

OPEN_PORT=$6
OPEN_TIME=$7




PHASE_1=p1_$KNOCK_NAME
PHASE_2=p2_$KNOCK_NAME
PHASE_3=p3_$KNOCK_NAME
PHASE_4=p4_$KNOCK_NAME

JUMP_TO_2=j2_$KNOCK_NAME
JUMP_TO_3=j3_$KNOCK_NAME
JUMP_TO_4=j4_$KNOCK_NAME


iptables -X $JUMP_TO_2
iptables -N $JUMP_TO_2
iptables -A $JUMP_TO_2 -m recent --name $PHASE_1 --remove
iptables -A $JUMP_TO_2 -m recent --name $PHASE_2 --set

iptables -X $JUMP_TO_3
iptables -N $JUMP_TO_3
iptables -A $JUMP_TO_3 -m recent --name $PHASE_2 --remove
iptables -A $JUMP_TO_3 -m recent --name $PHASE_3 --set

iptables -X $JUMP_TO_4
iptables -N $JUMP_TO_4
iptables -A $JUMP_TO_4 -m recent --name $PHASE_3 --remove
iptables -A $JUMP_TO_4 -m recent --name $PHASE_4 --set


iptables -A INPUT                        -m recent --update --name $PHASE_1
iptables -A INPUT -p udp --dport $PORT_1 -m recent --set    --name $PHASE_1
iptables -A INPUT -p udp --dport $PORT_2 -m recent --rcheck --name $PHASE_1 -j $JUMP_TO_2
iptables -A INPUT -p udp --dport $PORT_3 -m recent --rcheck --name $PHASE_2 -j $JUMP_TO_3
iptables -A INPUT -p udp --dport $PORT_4 -m recent --rcheck --name $PHASE_3 -j $JUMP_TO_4

iptables -A INPUT -p tcp --dport $OPEN_PORT -m recent --rcheck --seconds $OPEN_TIME --name $PHASE_4 -j ACCEPT
(credit: http://www.debian-administration.org/articles/268)

In this example I'm actually using UDP ports, because they are easier to knock on: no attempt is made to actually connect and (thus) timeout. If you prefer TCP knocking, and you don't know how to adjust the script, click here.


# setup all rules you currently have
... (important stuff!)

# add (several) port knocking triggers
./iptables-knocking.sh mail2 108 107 106 105 9992 15
./iptables-knocking.sh mail3 101 102 103 104 9993 15
                         |    |   |   |   |   |   -> timeout_to_close
                       name   |   | port3 | port_to_open
                            port1 |       |
                               port2    port4

# reject everything else
iptables -A INPUT -s 0/0 -d 0/0 -j REJECT

When working with IPTABLES, keep paying attention. It's WAY too easy to lock yourself out or 'ruin your uptime' one way or another. Needless to say, I am in no way responsible for any problems or damages that are the result of the usage of the above code. You have been warned.


Next up, a trivial Java portknocking client:
public static void knockUDP(String hostname, int... ports) {
    DatagramSocket socket = new DatagramSocket();
    for(int port: ports) {

       // account for a bit of packet loss
       for(int i=0; i<4; i++) {
          socket.send(new DatagramPacket(new byte[1], 1, new InetSocketAddress(hostname, port)));
       }

       // try to make it more likely the packets arrive in-order
       Thread.sleep(100);
    }
}

public static void knockTCP(String hostname, int... ports) {
    for(int port: ports) {
       Socket s = new Socket();
       try {
          s.connect(new InetSocketAddress(hostname, port), 100);
          s.close(); // it appears we actually connected, which is not what we wanted....
       } catch( IOException) {
          // ignore, really!
       }
    }
}

knockUDP("your-public-server.com", 101, 102, 103, 104);
knockTCP("your-public-server.com", 101, 102, 103, 104);

Reverse SSH tunnel in plain english...

Those tutorials explaining reverse SSH tunnels are ambiguous at best in their examples, often using the same port number twice, making it unclear whether it is a local port or a remote port.

Yes, I'm done complaining, here's my attempt at being helpful:

MASTER_HOST=where-your-ssh-server-is.com
MASTER_PORT=22 # likely
MASTER_USER=root # not really!
MASTER_LISTEN=... # whatever you want your public port to be
TARGET_HOST=192.168.... # probably some local server
TARGET_PORT=25 # let's imagine we tunnel a mailserver

# let's actually do something now!
ssh -l $MASTER_USER -nNT -p $MASTER_PORT -R $MASTER_LISTEN:$TARGET_HOST:$TARGET_PORT $MASTER_HOST

Please ensure the line 
   GatewayPorts yes
exists in the file
   /etc/ssh/sshd_config
or your tunnel will bind to localhost only.


As you can see, the order of parameters is not quite intuitive, which might have brought you on this page in the first place. Enjoy.