Wednesday, June 5, 2013

Using iptables logging and fail2ban to stop portscanning

I had previously mentioned using fail2ban here. After spinning up a server with a popular VPS provider, I noticed fairly quickly that, in addition to piles of login attempts against ssh on port 22, I also was getting a lot of portscans. Since I only had port 22 open in the firewall and DROPped the rest (apparently that's arguably anti-social, but whatever), it wasn't a big deal, but was kind of irritating.

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:

 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.

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.


  1. When I'm trying to restart fail2ban after adding everything I got this:

    [....] 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/", line 68, in getOptions
    return self.__jails.getOptions(jail)
    File "/usr/share/fail2ban/client/", line 67, in getOptions
    ret = jail.getOptions()
    File "/usr/share/fail2ban/client/", line 78, in getOptions
    ret =
    File "/usr/share/fail2ban/client/", line 56, in read
    return, "filter.d/" + self.__file)
    File "/usr/share/fail2ban/client/", line 62, in read, [bConf, bLocal])
    File "/usr/share/fail2ban/client/", line 108, in read
    fileNamesFull += SafeConfigParserWithIncludes.getIncludes(filename)
    File "/usr/share/fail2ban/client/", line 79, in getIncludes
    File "/usr/lib/python2.7/", line 305, in read
    self._read(fp, filename)
    File "/usr/lib/python2.7/", 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='

    1. I have tried it on different distros like Ubuntu LTS, Debian Wheezy...

    2. This comment has been removed by the author.

  2. This comment has been removed by the author.

  3. I think you're missing the word [Definiton] at the front. Adding that fixed it for me.

  4. fail2ban, fails2start after i add the [portscan] jail into jail.conf @ debian

  5. Apologies for this. I haven't tested this on debian or ubuntu, and don't have anything handy to test on.

  6. This 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.

    # Jail.local settings
    enabled = true
    action = iptables-allports[name=portscan]
    filter = portscan
    logpath = /var/log/ufw.log
    maxretry = 3

    # Fail2Ban port scan filter
    failregex = ^.*SRC=\sDST=.*$

    ignoreregex = ^.*SRC=\s.*DPT=(80|3306|443)\s.*$

    1. 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=

  7. FWIW, 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.

  8. Thank you for your work, I find your blog very informative
    Richard Brown best data rooms