UPDATE: Adding a link to a text file version of the script.
http://www.vulninfo.com/block_brute_force.txt

Recently I was looking for a script to block SSH brute force attempts to the device itself but all I found was iptables connection threshold based rules. This is okay, but that means if I make multiple legitimate connections I could still get blocked.

So I wrote the script below to add IPs to the block list based on failed authentication log entries.

First you should setup a log specifically for failed authentication that this script will parse.

/system logging action add memory-lines=1000 memory-stop-on-full=no name=failedauth target=memory
/system logging add action=failedauth disabled=no prefix="" topics=critical,system,error

Then add the script under /system scripts. I used Winbox to do this, although I am sure you CAN do it at the command line in RouterOS, I am not that hardcore.

# Script Name: SSH Block by log
# This script reads a specified log buffer (logBuffer)  At each log entry read,
# any IP exceeding the number below (failthreshold) is added to the address list below (blacklist)
# The log buffer is then cleared, so only new entries are read each time this script gets executed.
#/system logging action add memory-lines=1000 memory-stop-on-full=no name=failedauth target=memory
#/system logging add action=failedauth disabled=no prefix="" topics=critical,system,error

# Set this to a "memory" action log buffer. Example commands above.
:local logBuffer "failedauth"
:local failthreshold 5
:local blacklist "ssh_blacklist"

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

:local attackiparray {0}
:local attackcountarray {0}
:local logEntryTopics
:local logEntryTime
:local logEntryMessage
:local clearedbuf
:local lines
:set clearedbuf 0

:local i 0
:foreach rule in=[/log print as-value where buffer=($logBuffer)] do={
# Now all data is collected in memory..

# Clear log buffer right away so new entries come in
:if ($clearedbuf = 0) do={
/system logging action {
:set lines [get ($logBuffer) memory-lines]
set ($logBuffer) memory-lines 1
set ($logBuffer) memory-lines $lines
}
:set clearedbuf 1
}
# End clear log buffer

:set logEntryTime ""
:set logEntryTopics ""
:set logEntryMessage ""

:set logEntryTime ($rule->"time")
:set logEntryTopics ($rule->"topics")
:set logEntryMessage ($rule->"message")

:if ($logEntryMessage~"login failure") do={

:local attackip [:pick $logEntryMessage ([:find $logEntryMessage "from "]+5) ([:find $logEntryMessage " via"])]

:local x 0
:foreach ip in=$attackiparray do={
:if ($ip = $attackip) do={
:set ($attackcountarray->$x) (($attackcountarray->$x)+1)
} else={
:set ($attackiparray->$i) $attackip
:set ($attackcountarray->$i) 1
}
:set x ($x+1)
}

}

:set i ($i+1)
# end foreach rule
}
:local z 0
:foreach ip in=$attackiparray do={
:if ($attackcountarray->$z > $failthreshold) do={
:set ($attackcountarray->$z) 0
/ip firewall address-list add address=($attackiparray->$z) list=$blacklist
}
:set ($attackcountarray->$z) 0
:set z ($z+1)
}

Now you need to setup a “schedule” to run the script. Again I did this with Winbox. The timing of the schedule does matter though. Basically it is the number of failed login attempts since the last time the script was run. In this case I used five (5) minutes. The schedule only has to run the command below, replacing the script name below with whatever you named your script.

/system script run ssh_bruteforce_block

Finally you need a filter rule to drop any IP on the blacklist. The command below will create the filter rule to block any IP on the blacklist coming into ethernet1 on port 22 (on the input chain). Please make sure to put this above your allow rule in the list.

/ip firewall filter add action=drop chain=input comment="drop ssh brute forcers" dst-port=22 in-interface=ether1 protocol=tcp src-address-list=ssh_blacklist

I have been running this for a while and it works as intended, but please let me know if you find any issues. Useful?

If you haven’t heard of these wonderful devices, check them out http://www.mikrotik.com/ and on Twitter @mikrotik_com