MikroTik firewall address lists
The office edge router is a MikroTik running RouterOS 6. Its firewall works much like iptables, but the feature I lean on most is the address list: a named set of addresses that rules can match against, and that rules can also add to, with a timeout. That second part is what makes it useful. A rule can spot something odd, drop the source address into a list for twelve hours, and a later rule drops everything from that list.
Everything below goes in from a terminal session (SSH or the Winbox terminal). The WAN interface here is called ether1-wan; change it to whatever yours is.
Start with established traffic
Put these at the top of the input and forward chains so that replies to connections you started are never caught by the rules further down:
/ip firewall filter
add chain=input action=accept connection-state=established,related comment="accept established"
add chain=forward action=accept connection-state=established,related comment="accept established"
I leave the usual "drop invalid" rule until after the port-scan section. Stealth scan packets are invalid as far as connection tracking is concerned, so dropping invalid traffic first would stop them before the rules that record who sent them.
Static lists
Three lists that never change on their own: addresses that should never appear as a source on the internet side, your own public addresses, and your internal networks.
/ip firewall address-list
add list=bogons address=0.0.0.0/8 comment="this network"
add list=bogons address=10.0.0.0/8 comment="RFC 1918"
add list=bogons address=127.0.0.0/8 comment="loopback"
add list=bogons address=169.254.0.0/16 comment="link local"
add list=bogons address=172.16.0.0/12 comment="RFC 1918"
add list=bogons address=192.168.0.0/16 comment="RFC 1918"
add list=bogons address=224.0.0.0/4 comment="multicast"
add list=bogons address=240.0.0.0/4 comment="reserved"
add list=our-public address=203.0.113.10 comment="office line"
add list=our-public address=203.0.113.11 comment="server line"
add list=internal-nets address=192.168.10.0/24 comment="office LAN"
add list=internal-nets address=192.168.20.0/24 comment="server LAN"
Watch the masks. 172.16.0.0/12 is the whole private block; /20 only covers a sixteenth of it, and that is an easy typo to carry around for years.
Drop spoofed sources
Nothing arriving on the WAN port should claim to come from a private range, and nothing should claim to come from your own public addresses either. If it does, it is forged.
/ip firewall filter
add chain=input action=drop in-interface=ether1-wan src-address-list=bogons comment="bogons inbound"
add chain=input action=drop in-interface=ether1-wan src-address-list=our-public comment="our addresses inbound, spoofed"
add chain=forward action=drop in-interface=ether1-wan src-address-list=bogons comment="bogons inbound"
add chain=forward action=drop in-interface=ether1-wan src-address-list=our-public comment="our addresses inbound, spoofed"
Rate-limit ICMP
I want the router to answer pings, just not floods. The first rule accepts ICMP up to five packets a second with a burst of five. Anything over that falls through to the second rule, which adds the sender to icmp-flood for twelve hours, and the third rule drops them.
/ip firewall filter
add chain=input action=accept protocol=icmp limit=5,5 comment="ICMP within limit"
add chain=input action=add-src-to-address-list protocol=icmp address-list=icmp-flood address-list-timeout=12h comment="ICMP over limit"
add chain=input action=drop protocol=icmp src-address-list=icmp-flood comment="drop ICMP flooders"
Order matters: the accept has to come first or everyone ends up in the list.
Port knocking for Winbox
Winbox (TCP 8291) is the management port, and I don't want it open to the world. A port knock makes the router open it only to an address that has just touched a sequence of other ports in the right order.
/ip firewall filter
add chain=input action=add-src-to-address-list protocol=udp dst-port=4711 address-list=knock-1 address-list-timeout=15s comment="knock 1"
add chain=input action=add-src-to-address-list protocol=udp dst-port=3322 src-address-list=knock-1 address-list=knock-2 address-list-timeout=15s comment="knock 2"
add chain=input action=add-src-to-address-list protocol=tcp dst-port=5544 src-address-list=knock-2 address-list=knock-ok address-list-timeout=5h comment="knock 3, open"
add chain=input action=accept protocol=tcp dst-port=8291 src-address-list=knock-ok comment="Winbox for knockers"
add chain=input action=drop protocol=tcp dst-port=8291 comment="Winbox for everyone else"
Each step only counts if the previous list already holds the address, and each step has to happen within 15 seconds of the last. The final step grants five hours. Pick your own ports; the ones above are only examples. From a Linux machine the knock is three quick packets:
nc -u -w1 router.example.com 4711 < /dev/null
nc -u -w1 router.example.com 3322 < /dev/null
nc -w1 router.example.com 5544
If you have a fixed address at home, add it to knock-ok as a static entry (no timeout) and skip the knock. I used the same pattern to guard a web admin port on 8443.
Port scans
RouterOS has a port-scan detector, psd. The arguments are a weight threshold, a delay threshold, and the weights for low and high ports. psd=21,3s,3,1 means: if one address hits enough ports in three seconds that the weights add up to 21, treat it as a scan. The other rules catch the TCP flag combinations that a real stack never sends but nmap's stealth scans do.
/ip firewall filter
add chain=input action=add-src-to-address-list protocol=tcp psd=21,3s,3,1 in-interface=ether1-wan src-address-list=!internal-nets address-list=port-scan address-list-timeout=2w comment="psd"
add chain=input action=add-src-to-address-list protocol=tcp tcp-flags=fin,!syn,!rst,!psh,!ack,!urg address-list=port-scan address-list-timeout=2w comment="FIN scan"
add chain=input action=add-src-to-address-list protocol=tcp tcp-flags=fin,syn address-list=port-scan address-list-timeout=2w comment="SYN/FIN"
add chain=input action=add-src-to-address-list protocol=tcp tcp-flags=syn,rst address-list=port-scan address-list-timeout=2w comment="SYN/RST"
add chain=input action=add-src-to-address-list protocol=tcp tcp-flags=fin,psh,urg,!syn,!rst,!ack address-list=port-scan address-list-timeout=2w comment="Xmas"
add chain=input action=add-src-to-address-list protocol=tcp tcp-flags=fin,syn,rst,psh,ack,urg address-list=port-scan address-list-timeout=2w comment="all flags"
add chain=input action=add-src-to-address-list protocol=tcp tcp-flags=!fin,!syn,!rst,!psh,!ack,!urg address-list=port-scan address-list-timeout=2w comment="NULL scan"
add chain=input action=tarpit protocol=tcp src-address-list=port-scan comment="tarpit scanners"
add chain=input action=drop src-address-list=port-scan comment="drop scanners"
add chain=forward action=drop src-address-list=port-scan comment="drop scanners to the LAN"
add chain=input action=drop connection-state=invalid comment="drop invalid"
add chain=forward action=drop connection-state=invalid comment="drop invalid"
tarpit answers the TCP handshake and then holds the connection open with a zero window, so the scanner wastes its time. It only handles TCP, so the drop rule after it picks up everything else from the same list. Two weeks is long, but a scanner rarely comes back from a different address within that time, and it keeps the list from churning.
While I was there I also dropped the Windows file-sharing ports from the internet outright:
add chain=forward action=drop protocol=tcp port=135-139 in-interface=ether1-wan comment="no NetBIOS from outside"
Slowing down SSH brute forcing
This is the staged pattern from the MikroTik wiki. Every new SSH connection moves the source one list further along. Each stage only lasts a minute, so someone who connects four times inside a few minutes lands in the blacklist for ten days, while a person who logs in once a day never gets past stage one.
/ip firewall filter
add chain=input action=drop protocol=tcp dst-port=22 src-address-list=ssh-blacklist comment="drop SSH brute forcers"
add chain=input action=add-src-to-address-list protocol=tcp dst-port=22 connection-state=new src-address-list=ssh-stage3 address-list=ssh-blacklist address-list-timeout=10d
add chain=input action=add-src-to-address-list protocol=tcp dst-port=22 connection-state=new src-address-list=ssh-stage2 address-list=ssh-stage3 address-list-timeout=1m
add chain=input action=add-src-to-address-list protocol=tcp dst-port=22 connection-state=new src-address-list=ssh-stage1 address-list=ssh-stage2 address-list-timeout=1m
add chain=input action=add-src-to-address-list protocol=tcp dst-port=22 connection-state=new address-list=ssh-stage1 address-list-timeout=1m
The rules are deliberately in reverse order. A packet is checked against stage 3 before stage 2, so one connection only moves one step. Put them the other way up and a single connection walks straight through all four.
The same wiki page has a version for FTP that watches the router's own outgoing 530 Login incorrect replies; it only works for an FTP server running on the router itself.
Checking what's in the lists
/ip firewall address-list print where list=port-scan
/ip firewall address-list print count-only where list=ssh-blacklist
/ip firewall address-list remove [find list=icmp-flood address=198.51.100.23]
The last line is the one you'll need when you lock yourself out by pinging the router too enthusiastically. Keep a second session open whenever you change firewall rules, or use safe mode (Ctrl+X in the terminal), which rolls back your changes if the session drops.
Reference: https://wiki.mikrotik.com/wiki/Bruteforce_login_prevention