Port Knocking

Port knocking is a fun tactic for exposing otherwise hidden services to a client that can present a shared secret in the form of a correct series of particular packets. Since the server end of the scheme is typically implemented with Netfilter the number of ways the authentication can be complicated is limited only by Netfilter's capacity to filter packets. For example you could choose to implement a scheme that requires packets of various sizes, header configurations or payloads. In its simplest and most common form the client sends a tcp SYN packet to a series of ports at the destination address in a specific sequence known only to the client and server. This allows an authorized user to gain access from virtually any machine equipped with any number of TCP based, port-configurable clients including telnet, avoiding the inherent problems of requiring specialty software or custom scripts.

#!/bin/sh # # http://foxpa.ws/port-knocking/ # # Uncomment LOG rules to monitor client activity via syslog. PORT1=1111 PORT2=2222 PORT3=3333 PORT4=4444 PROTECTED=22 TIMEOUT=60 iptables -N KNOCKP1 iptables -A KNOCKP1 -m recent --name KNOCKP1 --set #iptables -A KNOCKP1 -j LOG --log-prefix "KNOCK PHASE1: " iptables -N KNOCKP2 iptables -A KNOCKP2 -m recent --name KNOCKP1 --remove iptables -A KNOCKP2 -m recent --name KNOCKP2 --set #iptables -A KNOCKP2 -j LOG --log-prefix "KNOCK PHASE2: " iptables -N KNOCKP3 iptables -A KNOCKP3 -m recent --name KNOCKP2 --remove iptables -A KNOCKP3 -m recent --name KNOCKP3 --set #iptables -A KNOCKP3 -j LOG --log-prefix "KNOCK PHASE3: " iptables -N KNOCKP4 iptables -A KNOCKP4 -m recent --name KNOCKP3 --remove iptables -A KNOCKP4 -m recent --name KNOCKP --set #iptables -A KNOCKP4 -j LOG --log-prefix "KNOCK PHASE4: " iptables -N KNOCK iptables -A KNOCK -p tcp -m tcp --dport $PORT1 -m conntrack --ctstate NEW -j KNOCKP1 iptables -A KNOCK -p tcp -m tcp --dport $PORT2 -m conntrack --ctstate NEW -m recent --rcheck --seconds $TIMEOUT --reap --name KNOCKP1 -j KNOCKP2 iptables -A KNOCK -p tcp -m tcp --dport $PORT3 -m conntrack --ctstate NEW -m recent --rcheck --seconds $TIMEOUT --reap --name KNOCKP2 -j KNOCKP3 iptables -A KNOCK -p tcp -m tcp --dport $PORT4 -m conntrack --ctstate NEW -m recent --rcheck --seconds $TIMEOUT --reap --name KNOCKP3 -j KNOCKP4 iptables -A INPUT ! -s -j KNOCK iptables -A INPUT -p tcp -m tcp -dport $PROTECTED -m conntrack --ctstate NEW -m recent --rcheck --seconds $TIMEOUT --name KNOCK --reap -j ACCEPT iptables -A INPUT -p tcp -m tcp -dport $PROTECTED -m conntrack --ctstate NEW -j REJECT

We can use netcat or nmap to send our knocks:
$ nc -z -w1 hostname port $ nmap -Pn --host_timeout 1 --max-retries 0 -p port hostname

It's easy to create a one-liner using shell loops:
$ for x in 1111 2222 3333 4444; do nc -z -w1 hostname $x && sleep 1; done && ssh hostname

You can add this to your OpenSSH client configuration at ~/.ssh/config (create the file if it does not exist) so that it runs every time you attempt to connect to host.alias:
Host host.alias HostName hostname ProxyCommand sh -c "for x in 1111 2222 3333 4444; do nc -z -w1 %h $x; done && nc %h %p"

Mitigating Connection Flooding/DoS and Brute Force Attacks with Netfilter (iptables)

The simplest thing we can do to throw off an automated attack is to initially throw a failure state. While a lot of legitimate client software is designed to be tolerant (i.e. retry the connection if the first attempt fails) attackers have no reason to consider user friendliness when they design their bots and poking at a seemingly unresponsive host is a waste of time that could be better spent on the next target.

We can use Netfilter to introduce a crude delay (a single knock port knock, if you will...) to new connections and even if our attacker sticks around intentional delays severely limit the effectiveness of brute force cracking:
iptables -A INPUT -p tcp -i eth0 -m conntrack --ctstate NEW --dport 22 -m recent --update --seconds 15 -j DROP iptables -A INPUT -p tcp -i eth0 -m conntrack --ctstate NEW --dport 22 -m recent --set -j ACCEPT

We can be more ideologically correct by monitoring new connections and drop them when there have been too many in a given time frame. By using two thresholds we can immediately block fast-acting attackers before they get more than a few tries in and take out rate-limited bots that generate a lot of attempts over a longer time without (hopefully) intercepting users that have forgotten their password:
iptables -N IN_SSH iptables -A INPUT -p tcp --dport ssh -m conntrack --ctstate NEW -j IN_SSH iptables -A IN_SSH -m recent --name sshbf --rttl --rcheck --hitcount 3 --seconds 10 -j DROP iptables -A IN_SSH -m recent --name sshbf --rttl --rcheck --hitcount 10 --seconds 1800 -j DROP iptables -A IN_SSH -m recent --name sshbf --set -j ACCEPT

We may need to limit the number of connections to a service, particularly if establishing a new connection is a very resource-intense proposition.
iptables -A INPUT -p tcp --dport 1935 -m conntrack --ctstate NEW -m limit --limit 5/second --limit-burst 20 -j ACCEPT iptables -A input -p tcp --dport 1935 -m conntrack --ctstate NEW -j DROP

The limit module applies to packets in general and wouldn't apply to whole connections if we weren't specifying the NEW state; as the name implies it's more suited to rate limiting the speed of traffic flows. Our limit rule also doesn't care how many connections are coming from each individual address. A better way to implement whole-connection limiting might be with the connlimit module:
iptables -A INPUT -p tcp -m tcp --dport 1935 -m connlimit --connlimit-above 5 --connlimit-mask 32 -j REJECT --reject-with tcp-reset
--connlimit-mask refers to the subnet mask, meaning this rule will apply to indivudal IPs when set to 32. We can broaden it to apply to an entire class C subnet by changing it to 24.

MAC Address Filtering with iptables

Source and destination MAC addresses are included in the header of every Ethernet (and by extension, WiFi) frame. As the name implies, iptables is generally intended for use with Layer 3 protocols and as such has limited support for dealing with MAC addresses. Layer 2 filtering is more properly the domain of arptables and ebtables. That being said, iptables does have a limited 'mac' module that lets us filter by --mac-source which lets us do some neat things to local packets received on Ethernet/WiFi interfaces.

For example, you may wish to ignore all traffic from a particular MAC on your network:
iptables -A INPUT -m mac --mac-source 12:34:56:78:9a:bc -j DROP

Or flip it around with the ! operator and discard all traffic from any device on your local network other than your upstream gateway:
iptables -A INPUT -m mac ! --mac-source 12:34:56:78:9a:bc -j DROP

You might find it handy to allow SSH access only to one device on a particular interface, avoiding having to specify any IP addresses which may be ideal on a DHCP-configured or frequently changing LAN:
iptables -A INPUT -i eth0 --dport 22 -m mac ! --mac-source 12:34:56:78:9a:bc -j REJECT

If you want to set up an ACL similar to a hotspot whitelist/blacklist it may be easier to maintain a list of addresses in a text file:
for MAC in `cat /path/to/whitelist.txt`; do iptables -A FORWARD -i eth0 -o eth1 -m mac --mac-source $MAC -j ACCEPT done iptables -P FORWARD DROP
It is possible to defend against ARP poisoning/spoofing by taking this one step further and dropping all traffic coming from IPs that don't match their proper MAC address if you maintain a table of every device you wish to communicate with, statically configured their IP addresses or set up static leases in your DHCP server.

It should be noted that since the only filter available to us is the MAC source address we can only create useful filters in the INPUT, FORWARD and PREROUTING chains.

You can find a device's MAC address locally using ifconfig:
# ifconfig eth0 eth0: flags=4163 mtu 1500 inet netmask broadcast ether 12:34:56:78:9a:bc txqueuelen 1000 (Ethernet) RX packets 1035457 bytes 733963916 (699.9 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 1183022 bytes 909429182 (867.2 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

...or ip:
# ip addr show eth0 2: eth0: mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 12:34:56:78:9a:bc brd ff:ff:ff:ff:ff:ff

...or ethtool:
# ethtool -P eth0 Permanent address: 12:34:56:78:9a:bc

You can find a remote device's MAC address by running arping:
# arping ARPING 60 bytes from 12:34:56:78:9a:bc ( index=0 time=775.829 usec ...

Alternatively, since arping is not always available (or you may be using a Windows machine...) you can send a conventional ICMP echo request to the device to ensure a record is entered into your local ARP table and obtain its address there:
# ping PING ( 56(84) bytes of data. 64 bytes from icmp_seq=1 ttl=255 time=1.95 ms ^C --- ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 1.947/1.947/1.947/0.000 ms # arp -a | grep ? ( at 12:34:56:78:9a:bc [ether] on eth0

Shell Scripting Variable Cheat Sheet

There are myriad implementations of the common UNIX shell. Often these shells are syntactically compatible and the following rules tend to apply when dealing with variables:

  • Names must be alphanumeric and may not start with a number.
  • Names are case sensitive and conventionally upper-case.
  • There may be no whitespace between the variable name, assignment operator and value when a variable is set.
  • Variable names begin with the $ character when referred to in read operations; this character is omitted when they are set.
  • Variables are scoped within the script they are defined in, unless they are exported.
  • Variables may be flagged readonly upon initialization.
  • Command substitution assigns the output of commands contained in $() at the time the variable is set.
  • Variables may be unset via the unset command.

MYVAR=1 COMPLEXVAR="A string containing spaces." readonly ROVAR="This value can not be changed later." CSVAR=$( ls /var | wc -l )
Special variables dynamically made available at runtime:

  • $0 - The name of the script.
  • $1 - $9 - The first 9 arguments to the script.
  • $# - How many arguments were passed to the script.
  • [email protected] - All the arguments supplied to the script.
  • $? - The exit status of the most recently run process.
  • $$ - The process ID of the current script.

You can see additional variables currently available to you by issuing env or export without arguments at the command line or from inside your script.
$ env SHELL=/bin/bash LANGUAGE=en_US.UTF-8 NO_AT_BRIDGE=1 PWD=/home LOGNAME=username XDG_SESSION_TYPE=tty HOME=/home/username LANG=en_CA.UTF-8 XDG_SESSION_CLASS=user TERM=xterm-256color USER=username SHLVL=1 XDG_SESSION_ID=c16 XDG_RUNTIME_DIR=/run/user/1000 LC_ALL=en_US.UTF-8 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus MAIL=/var/mail/username SSH_TTY=/dev/pts/4 TEXTDOMAIN=Linux-PAM _=/usr/bin/env

Special variables available to BASH scripts:

  • $EPOCHSECONDS - Each time this parameter is referenced it expands to the number of seconds since the Unix Epoch. (eg: 1577354438)
  • $EPOCHREALTIME - Same as $EPOCHSECONDS but includes microseconds. (eg: 1577354438.788521)
  • $SECONDS - Expands to the number of seconds since the shell was started. Assignment to this variable resets the count to the value assigned, and the expanded value becomes the value assigned plus the number of seconds since the assignment.
  • $RANDOM - Each time this parameter is referenced, a random integer between 0 and 32767 is generated. Assigning a value to this variable seeds the random number generator.
  • $LINENO - Returns the current line number in the Bash script.
  • $MACHTYPE - A string that fully describes the system type on which Bash is executing, in the standard GNU cpu-company-system format. (eg: arm-unknown-linux-gnueabihf)
  • $OSTYPE - A string describing the operating system Bash is running on. (eg: linux-gnueabihf)

Additional BASH-specific variables can be found at the BASH Reference Manual.

You can find a more in-depth look at variable syntax and example use cases at https://ryanstutorials.net/bash-scripting-tutorial/bash-variables.php.

Reset Forgotten Raspberry Pi Password

Changing a lost/forgotten Raspbian/Ubuntu MATE/etc password on a Raspberry Pi is a slightly different procedure than you are likely used to. There is no GRUB bootloader menu to intercept so you can interactively edit the kernel command line. Booting off a live cd/usb stick to chroot on another machine isn't going to work unless that machine uses the same ARM architecture and your boot environment uses a kernel that is compatible with your Pi's binaries.

  • Remove the SD card from your Raspberry Pi. Mount it on another machine.
  • Edit the cmdline.txt file and add init=/bin/sh to the end of the kernel command line.
  • Reinsert the SD card into the Pi and boot.
  • You will land at a root prompt (#). Issue the following command to remount the root filesystem in read/write mode: mount -o remount,rw /
  • If you are running Raspbian your username is likely "pi" - if you are running Ubuntu MATE or another flavour it will be whatever you chose at the time of installation. Issue the following to update the password: passwd <user>.
  • Launch init: exec /sbin/init. You should see the rest of your normal boot process complete and then be able to log in with your new password as usual.
  • With root privileges (using sudo), edit /boot/cmdline.txt and remove the string init=/bin/sh that you added on the other machine.
  • Now reboot your Pi by issuing: reboot with root privileges; your regular boot process should run and you will be able to log in with the new password again.