Skip to content

Instantly share code, notes, and snippets.

@dublado
Created September 28, 2018 12:02
Show Gist options
  • Save dublado/3cfcdbbc94be34443803c456443fbbcb to your computer and use it in GitHub Desktop.
Save dublado/3cfcdbbc94be34443803c456443fbbcb to your computer and use it in GitHub Desktop.

Centralize Fail2Ban

Warding potential server attacks with a centralized Fail2Ban

System Requirements

This HowTo assumes that fail2ban, iptables, mysql and php is installed functional on the system. On Ubuntu, you can quickly do:

root@devserv3:~# sudo apt-get install php5 mysql-server fail2ban iptables

Next, we have to create a database in MySql, the database called "fail2ban". In this database, a table is created:

CREATE TABLE IF NOT EXISTS `fail2ban` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `hostname` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
  `created` datetime NOT NULL,
  `name` text COLLATE utf8_unicode_ci NOT NULL,
  `protocol` varchar(16) COLLATE utf8_unicode_ci NOT NULL,
  `port` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
  `ip` varchar(64) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  KEY `hostname` (`hostname`,`ip`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
        

PHP script for passing the fail2ban IP

Now comes a small PHP script to use, which will accept the IP address, port, protocol, and the fail2ban Jailnamen and writes to the MySQL database.
The PHP script is created in the directory: /root and we make it executable. In addition, read permissions should be revoked for unauthorized persons!

root@devserv3:~# cd /root && touch fail2ban.php && chmod +x fail2ban.php

fail2ban.php is filled with the following content:

#!/usr/bin/php -n
<?php
$name = $_SERVER["argv"][1];
$protocol = $_SERVER["argv"][2];
$port = $_SERVER["argv"][3];
if (!preg_match('/^\d{1,5}$/', $port))
    $port = getservbyname($_SERVER["argv"][3], $protocol);
$ip = $_SERVER["argv"][4];

$hostname = gethostname();

// connect to mysql by hostname, username and password
$link = mysql_connect('devserv3', 'fail2ban', 'password') or die('Could not connect: ' . mysql_error());
mysql_select_db('fail2ban') or die('Could not select database');
$query = 'INSERT INTO `fail2ban` set hostname="' . addslashes($hostname) . '", name="' . addslashes($name) . '", protocol="' . addslashes($protocol) . '", port="' . addslashes($port) . '", ip="' . addslashes($ip) . '", created=NOW()';
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
mysql_close($link);
exit;

Now you can test the script and check if datas in database arrive.

root@devserv3:~# ./fail2ban.php jailname ssh 22 123.123.123.123

Connection to Fail2Ban

If the script works we can create the connection to Fail2Ban. To do this, change to the fail2ban configuration directory.

root@devserv3:~# cd /etc/fail2ban/ && ls -al
total 28
drwxr-xr-x  4 root root 4096 Feb 13 14:56 .
drwxr-xr-x 97 root root 4096 Feb 13 15:28 ..
drwxr-xr-x  2 root root 4096 Feb 13 14:56 action.d
-rw-r--r--  1 root root  853 Nov 29  2011 fail2ban.conf
drwxr-xr-x  2 root root 4096 Feb 13 14:56 filter.d
-rw-r--r--  1 root root 7346 Jun 18  2013 jail.conf
root@devserv3:/etc/fail2ban#

In the files jail.conf and jail.local (must not be present), the jails and the banaction are defined. Here once the example from Jail "pam-generic".

[pam-generic]
enabled  = true
filter   = pam-generic
port     = all
banaction = iptables-multiport
logpath  = /var/log/auth.log

The line with the banaction sets with which action the IP to be treated.
If no banaction at Jail specified, the default banaction is used. This is defined in the same file, in the [DEFAULT]. In general, the action is the iptables-multiport.
In the action folder "action.d" the file iptables multiport.conf should be found.
This file is now extended that, when a IP is banned then also our PHP script is called. Calling all parameters such as IP, port, etc. are passed.
After the line with actionban = ..... a new row inserted to invoke the PHP script:

actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP /root/fail2ban.php <name> <protocol> <port> <ip>

Now fail2ban even have to be restarted. In the fail2ban logs can be checked whether there was an error.

root@devserv3:~# /etc/init.d/fail2ban restart
root@devserv3:~# cat /var/log/fail2ban.log

From now on, intrusions are detected and logged in the database.
In the second part of the HOWTO (coming soon) will be described how to distribute the IP addresses from the database on another server.

Now the Fail2Ban configuration file will be prepared. For this purpose, the supplied with Fail2Ban file is copied and edited (Nano is the editor) opened.

cd /etc/fail2ban && cp jail.conf jail.local && nano jail.local

In the jail.local a jail at the end of the default block is inserted. The name of the jail "blocklist"

[blocklist]
enabled  =  true
port     =  ssh
filter   =  sshd
logpath  =  /etc/fail2ban/empty.log
maxretry =  1
banaction = iptables-allports
action   = %(action_)s

With this blocklist we define a file /etc/fail2ban/empty.log to be monitored. The maximum break-in attempt is set to 1, so that the first appearance of an IP address will be blocked. The type of blocking is set with banaction and means in the example a blocking of the IP on all ports.
For the attacker behind the locked IP, the server is no longer accessible. Here it is important to ensure that you do not lock out yourself!

Now must Fail2Ban be restarted once and it can be tested whether Fail2Ban IP takes in the blocklist.
For this purpose, an IP address will be passed to Fail2Ban to be locked and a "-" is written into emptly-log to indicate a change to Fail2Ban. Then Fail2Ban will block the given IP.

root@devserv3:~# service fail2ban restart
 * Restarting authentication failure monitor fail2ban             [ OK ]
fail2ban-client set blocklist banip 1.168.100.1 echo "-" > /etc/fail2ban/empty.log;

If there was no error message has all worked out. If an error occurs take a look in the log file to get more informations (/var/log/fail2ban.log). In iptables, the IP should now be also listed.

root@devserv3:~# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
fail2ban-blocklist  tcp  --  anywhere             anywhere
fail2ban-ssh  tcp  --  anywhere             anywhere             multiport dports ssh

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain fail2ban-blocklist (1 references)
target     prot opt source               destination
DROP       all  --  1-168-100-1.dynamic.hinet.net  anywhere
RETURN     all  --  anywhere             anywhere

Chain fail2ban-ssh (1 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere

PHP script to get the IP from the database

Now a small PHP script is used, the read from the central database, the IP addresses.
To prevent that all IP addresses are repeatedly read from the database, the database column "created" be used as a helper. So only the entries of the last 60 seconds are fetched in the script.
The name of the script is fail2ban.get.php

#!/usr/bin/php -n
<?php
// connect to mysql by hostname, username and password
$link = mysql_connect('devserv3', 'fail2ban', 'password') or die('Could not connect: ' . mysql_error());
mysql_select_db('fail2ban') or die('Could not select database');
$query = 'SELECT ip FROM `fail2ban` where created>DATE_ADD(NOW(), INTERVAL -60 SECOND)';
$result = mysql_query($query) or die('Query failed: ' . mysql_error());
$cmd = null;
while ($row = mysql_fetch_object($result)) {
    $cmd .= 'fail2ban-client set blocklist banip ' . $row->ip . ' && ';
}
if ($cmd) {
    $cmd .= "echo \"-\" > /etc/fail2ban/empty.log";
    $cmd = $cmd . ' >/dev/null 2>/dev/null &';
    exec($cmd, $output, $returnValue);
}
mysql_close($link);
exit;

Now briefly test the script ....

root@devserv3:~# ./fail2ban.get.php

No error ? Great.
Now we put the script into a cron job, so it is called every minute and read IP's to lock. The call cycle can also raise, if it is should not be any minute - but then adjust accordingly the 60 seconds in the script.
We modify with nano this file: /etc/crontab and add the following at the end.

*/1 * * * * root nice -n 19 /usr/bin/php -c /etc/php5/apache2/php.ini -f /root/fail2ban.get.php > /dev/null 2>&1

Ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment