=^.^=

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 192.168.0.251 netmask 255.255.255.0 broadcast 192.168.0.255 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 192.168.0.1 ARPING 192.168.0.1 60 bytes from 12:34:56:78:9a:bc (192.168.0.1): 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 192.168.0.1 PING 192.168.0.1 (192.168.0.1) 56(84) bytes of data. 64 bytes from 192.168.0.1: icmp_seq=1 ttl=255 time=1.95 ms ^C --- 192.168.0.1 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 192.168.0.1 ? (192.168.0.1) 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.

Examples...
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.

Install killall on CentOS/RHEL

It seems killall has been removed from the default toolset on Red Hat distros (i.e. CentOS and RHEL). The logic is sound: as you probably know killall does potentially dangerous different things on other UNIX variants; chiefly it will kill every single process on Solaris/SunOS with wonton disregard.

The preferred weapon of choice is now pkill, as in:
# pkill victim
...where victim is the name of the process(es) you wish to terminate.

But if you're like me, you don't use Solaris and for nigh on 20 years you've been conditioned to reach for killall when you need to quickly hunt down and slaughter a runaway process on your linux hosts.

Creatures of habit die hard and in old age ought be afforded some luxuries, thus:
# yum install psmisc
and
# killall thepetulantbastards