Well, why can't I just configure fail2ban to block hosts that are hitting me with portscans? Why not, indeed!
fail2ban has a fairly simple format for filters. You need a [Definition] block, which consists of a chunk of line separated regexes for the error you are looking for, and potentially a chunk of line separated regexes for errors you'd like to ignore. This all goes into a filter file, which usually lives in /etc/fail2ban/filters.d.
My anti-portscan filter, creatively called "portscan.conf," is as follows:
# Option: failregex
# Notes: Looks for attempts on ports not open in your firewall. Expects the
# iptables logging utility to be used. Add the following to your iptables
# config, as the last item before you DROP or REJECT:
# -A <chain_name> -j LOG --log-prefix "PORT DENIED: " --log-level 5 --log-ip-options --log-tcp-options --log-tcp-sequence
# This will place a notice in /var/log/messages about any attempt on a port that isn't open.
failregex = PORT DENIED: .* SRC=<HOST>
This is pretty simple, the regex is just this: PORT DENIED: .* SRC=<HOST>
The <HOST> item indicates where the IP address of the attacker is expected.
In order to get these error messages, you need to take advantage of the iptables logging utility. iptables has the ability to log any connections, successful or not. Which connections it logs depends on where in the rules chain the LOG item is placed. If you were a lunatic, I'm sure you could place it first and log each and every connection that your system makes. Don't do this. It's dumb. For our purposes, we want a LOG item placed immediately before we finally DROP or REJECT a connection. All rules regarding opening a hole for a service should go PRIOR to the LOG entry. An example of a LOG item is:
-A <chain_name> -j LOG --log-prefix "PORT DENIED: " --log-level 5 --log-ip-options --log-tcp-options --log-tcp-sequence
where <chain_name> is the name of whatever chain you currently are in. The log-level and other options are mostly insignificant for the purposes of our fail2ban filter, but the log-prefix is meaningful. If you change the log prefix in the iptables config, make sure you update the regex in your filter to match.
The logging utility will then log any DROPped or REJECTed connection to /var/log messages.
Once we have our filter created, and our failed connections being logged, we can update the fail2ban config to make use of them.
Edit /etc/fail2ban/jail.conf (or, jail.local if you're on Ubuntu or an OS that prefers you to not use jail.conf) with the following:
[portscan]
enabled = true
filter = portscan
action = iptables[name=portscan]
logpath = /var/log/messages
maxretry = 3
You can just append it to the end of the file. This names the jail, enables it, tells it which filter to use and how to block it, and which log to parse. I have it set to fail after 3 attempts, you can customize that to suit your preferences.
Once iptables and fail2ban have been restarted to take advantage of the new configs, a server that gets hit with portscans regularly should start blocking connections. Note that many residential ISPs sniff out and shut down portscans, themselves, so you may not see this sort of traffic at home.
Edit:
Apparently this doesn't work out of the box on Ubuntu. I don't have an install handy, so I'm not sure what the differences are. The definition should still work, you just might have to do some other finagling.
When I'm trying to restart fail2ban after adding everything I got this:
ReplyDelete[....] Restarting authentication failure monitor: fail2banTraceback (most recent call last):
File "/usr/bin/fail2ban-client", line 404, in
if client.start(sys.argv):
File "/usr/bin/fail2ban-client", line 373, in start
return self.__processCommand(args)
File "/usr/bin/fail2ban-client", line 183, in __processCommand
ret = self.__readConfig()
File "/usr/bin/fail2ban-client", line 378, in __readConfig
ret = self.__configurator.getOptions()
File "/usr/share/fail2ban/client/configurator.py", line 68, in getOptions
return self.__jails.getOptions(jail)
File "/usr/share/fail2ban/client/jailsreader.py", line 67, in getOptions
ret = jail.getOptions()
File "/usr/share/fail2ban/client/jailreader.py", line 78, in getOptions
ret = self.__filter.read()
File "/usr/share/fail2ban/client/filterreader.py", line 56, in read
return ConfigReader.read(self, "filter.d/" + self.__file)
File "/usr/share/fail2ban/client/configreader.py", line 62, in read
SafeConfigParserWithIncludes.read(self, [bConf, bLocal])
File "/usr/share/fail2ban/client/configparserinc.py", line 108, in read
fileNamesFull += SafeConfigParserWithIncludes.getIncludes(filename)
File "/usr/share/fail2ban/client/configparserinc.py", line 79, in getIncludes
parser.read(resource)
File "/usr/lib/python2.7/ConfigParser.py", line 305, in read
self._read(fp, filename)
File "/usr/lib/python2.7/ConfigParser.py", line 512, in _read
raise MissingSectionHeaderError(fpname, lineno, line)
ConfigParser.MissingSectionHeaderError: File contains no section headers.
file: /etc/fail2ban/filter.d/portscan.conf, line: 7
'failregex = PORT DENIED: .* SRC='
I have tried it on different distros like Ubuntu LTS, Debian Wheezy...
DeleteThis comment has been removed by the author.
DeleteThis comment has been removed by the author.
ReplyDeleteI think you're missing the word [Definiton] at the front. Adding that fixed it for me.
ReplyDeletefail2ban, fails2start after i add the [portscan] jail into jail.conf @ debian
ReplyDeleteApologies for this. I haven't tested this on debian or ubuntu, and don't have anything handy to test on.
ReplyDeleteThis woks just fine on Ubuntu and Debian, but I'm using it with the UFW logs rather than iptables. I also had to add some ignoreregex for ports that are open. For some reason, known good IPs show up in the firewall logs as blocked for open ports sometimes. May be a timing issue when too many requests come in at once.
ReplyDelete# Jail.local settings
[portscan]
enabled = true
action = iptables-allports[name=portscan]
filter = portscan
logpath = /var/log/ufw.log
maxretry = 3
# Fail2Ban port scan filter
#
[Definition]
failregex = ^.*SRC=\sDST=.*$
ignoreregex = ^.*SRC=\s.*DPT=(80|3306|443)\s.*$
The HOST portion got left out of my regex above. Looks like your comments don't allow "html" tags. Should be a HOST in between greater than and less than signs right after SRC=
DeleteFWIW, there is one mnor flaw with this, but I'm pretty sure only a directed attack would get through. fail2ban only checks the logs every minute or so (tunable, I believe). An attacker could get through before the check blocks them, if they get in and drop a payload off before the script kicks off you'll be in trouble. It's mostly just protecting against skiddies, though, a determined attacker will take other routes if available.
ReplyDeleteThank you for your work, I find your blog very informative
ReplyDeleteRichard Brown best data rooms