Ban hammer, fail2ban geo ip on google maps

Though it would be fun to display the banned IP addressed on the server from fail2ban using google maps and geo ip, a quick google revealed this was already done so my solution is based on two following code bases for inspiration:-

https://github.com/martzuk/fail2ban-geo/

http://www.dbsysnet.com/howto-geolocation-for-fail2ban/

I started off with the dbsysnet code but modifed this to use google maps V3 API, then i stole the nice UI and map images and other stats ideas from fail2ban-geo, then rewrote the data fetcher to use JSON and ended up with the current version.

The current live version is at

http://static.byteme.org.uk/banhammer/

I assume you already have fail2ban set and working already, setting that up is outside the scope of this article. Next you need to install the fail2sql from http://fail2sql.sourceforge.net/ download the tarball and uncompress it to somewhere eg /usr/local/fail2sql/ I’ve made one small change to fail2sql as i wanted to record the timestamp that an entry got added to the database. So you need to modify the fail2sql database. Before you run the README instructions edit the file fail2ban.sql and add the following line

'timestamp' DATETIME,

AFTER the line

`geo` varchar(255) DEFAULT NULL,

You also want to modify lines 58 and 61 of the fail2sql script so that they now read

Line 58 :-

$query = "INSERT INTO `fail2ban` values ('', '".$name."', '".$protocol."', '".$port."', '".$ip."', '1', '".$geoip->longitude."', '".$geoip->latitude."', '".$geoip->country_code."', '".$geoip->city.", ".$geoip->country_name."', NOW())";

Line 61:-

$query = "INSERT INTO `fail2ban` values ('', '".$name."', '".$protocol."', '".$port."', '".$ip."', '1', '', '', '', '', NOW())";

The addition on both lines is a

,NOW()

at the end of the line, this just saves the current date and time when a new entry is added.

Next follow the README instructions that come with it. This tells you how to create the database and how to hook fail2sql into fail2ban.

NOTE, i’ve found one bug in the fail2sql README, if you are adding the fail2sql script to the iptables-multiport.conf then it will not work for any jails that have multiple ports, a quick hack is the following, on the actionban line where you add the fail2sql hook the tag will not work as if more than one port is defined to be blocked this will expand into each port seperated by a , the the fail2sql script does not cope with this. To fix this I just jammed this as ssh as I don’t use the field for anything as the tag gives the jail that was invoked anyway :-

actionban = iptables -I fail2ban- 1 -s  -j DROP
            /usr/local/fail2sql/fail2sql   ssh 

Now the juicy scripts

Download the complete set of html/php/javascript/css files here:- http://static.byteme.org.uk/banhammer.tar.gz

Firstly the data fetch scripts

Database include script – dbinfo.php

Set the user/password etc to your real values

$db_host = '127.0.0.1';
$db_user = 'fail2ban';
$db_pwd = 'fail2ban';

$database = 'fail2ban';
$table = 'fail2ban';

Marker fetch script – getmarker.php


//header("Content-type: application/json");
header("Content-type: text/text");

require("dbinfo.php");

// Start XML file, create parent node

// Opens a connection to a MySQL server

$connection=mysql_connect ($db_host, $db_user, $db_pwd);
if (!$connection) {  die('Not connected : ' . mysql_error());}

// Set the active MySQL database

$db_selected = mysql_select_db($database, $connection);
if (!$db_selected) {
  die ('Can\'t use db : ' . mysql_error());
}

$query = "SELECT * FROM $table";
$result = mysql_query($query);
if (!$result) {
  die('Invalid query: ' . mysql_error());
}

$rows = array();
while($r = mysql_fetch_assoc($result)) {
    $rows[] = $r;
}

print json_encode($rows);

Stats fetch script – getstats.php

header("Content-type: text/text");

require("dbinfo.php");

// Opens a connection to a MySQL server

$connection=mysql_connect ($db_host, $db_user, $db_pwd);
if (!$connection) {  die('Not connected : ' . mysql_error());}

// Set the active MySQL database

$db_selected = mysql_select_db($database, $connection);
if (!$db_selected) {
  die ('Can\'t use db : ' . mysql_error());
}

// Create an array to hold the data

$xx = array();

// Do each sql query and add teh results to the array

// Get total IPs logged
$xx['totalip'] = getdataset("SELECT COUNT(*) as count FROM $table");

// Get total number of countries
$xx['totalcountry'] = getdataset("SELECT COUNT(Distinct country) as count FROM $table");

// Get count for each protocol
$xx['protos'] = getdataset("SELECT name,COUNT(name) as count FROM $table");

// Get total counts for each country
$xx['totals'] = getdataset("SELECT country,COUNT(*) as count FROM $table GROUP BY country ORDER BY count DESC");

//Get last 5 Countires
$xx['last'] = getdataset("SELECT country,timestamp FROM $table ORDER BY timestamp DESC limit 5");

// Return the data in json
print json_encode($xx);

// ** DONE **

// Function to take an SQL query and return all results as a PHP array

function getdataset($query)
{

    $result = mysql_query($query);
    if (!$result) {
      die('Invalid query: ' . mysql_error());
    }

    $rows = array();
    while($r = mysql_fetch_assoc($result)) {
        $rows[] = $r;
    }

    return $rows;

}

index.html



Ban Hammer
  	<link href="css/styles.css" rel="stylesheet" type="text/css" /><script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script><meta name="viewport" content="initial-scale=1.0, user-scalable=no" /><script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAD8C3TFHJNCLfEAde4berZT8p-2n6OEvA&sensor=false"></script></pre>
<h2>Ban hammer</h2>
<div id="map_canvas"></div>
<div id="bottom_menu">
<div class="stat" id="statstat">
<strong><strong>Stats:
</strong></strong>
<ul>
	<li><strong>IPs blocked:</strong><span id="ipsblocked"></span></li>
	<li><strong>Countries blocked:</strong><span id="countriesclocked"></span></li>
</ul>
</div>
<div class="stat" id="statproto">
<strong>Protocols: </strong>
<ol id="protocols"></ol>
</div>
<div class="stat">
<strong>Latest 5 Blocked Countries: </strong>
<ol id="last5"></ol>
</div>
<div class="stat">
<strong>Top 5 Blocked Countries: </strong>
<ol id="top5"></ol>
</div>
</div>
<pre>


map.js

 var map;

      function clearOverlays() {
      for (var i = 0; i < markersArray.length; i++ ) {
      markersArray[i].setMap(null);
      }
        markersArray.length = 0;
      }

      function updatex()
      {
          clearOverlays();
          loadmarkers();
      }

      function loadmarkers()
      {

        $.getJSON("getmarkers.php", function( data ) {

          for (var i in data) {
            var name = data[i].geo;
            var address = data[i].ip;
            var type = data[i].name;
            var point = new google.maps.LatLng(parseFloat(data[i].latitude),parseFloat(data[i].longitude));
            createMarker(point, name, address, type);
          }
         });
  $.getJSON("getstats.php", function( data )
        {

                var totals = data['totals'];
                var countrys = data['last'];
                var protos = data['protos'];

                for (var i in totals)
                {
                        var country = totals[i].country;
                        var count = totals[i].count;
                        var name = country_code_to_country(country);
                        $("#top5").append("</pre>
<ul>
	<li><img title="\&quot;\&quot;" alt="\&quot;&quot;+country+&quot;\&quot;" src="\&quot;images/flags/&quot;+country+&quot;.png\&quot;" /> "+name+" ("+count+")"+"</li>
</ul>
<pre>

");
                }

                for (var i in countrys)
                {
                        var country = countrys[i].country;
                        var name = country_code_to_country(country);
                        $("#last5").append("</pre>
<ul>
	<li><img title="\&quot;\&quot;" alt="\&quot;&quot;+country+&quot;\&quot;" src="\&quot;images/flags/&quot;+country+&quot;.png\&quot;" /> "+name+"</li>
</ul>
<pre>

");
                }

                for (var i in protos)
                {
                        var name = protos[i].name;
                        var count = protos[i].count;

                        $("#protocols").append("</pre>
<ul>
	<li>"+name+" ("+count+")</li>
</ul>
<pre>
");
                }

                 $("#ipsblocked").html(" "+data['totalip'][0].count);
                 $("#countriesclocked").html(" "+data['totalcountry'][0].count);

        });

      }
  function createMarker(point, name, address, type) {

          words = type+" attack from "+address+"\n"+name;

          iname="markers/red_MarkerA.png"

          if(type=="wordpress")
          {
             iname="markers/blue_MarkerA.png"
          }
          if(type=="ssh")
          {
             iname="markers/yellow_MarkerA.png"
          }
          if(type=="dovecot")
          {
             iname="markers/green_MarkerA.png"
          }
          if(type=="sasl")
          {
             iname="markers/brown_MarkerA.png"
          }

          var marker = new google.maps.Marker({
                position: point,
                title:name,
                icon: iname
                });

          marker.setMap(map);

          var html = "<b>" + type+ " ATTACK" + "</b>
"+name + "
" + address;

          var infowindow = new google.maps.InfoWindow({
              content: html
          });

          google.maps.event.addListener(marker, 'click', (function() {
              infowindow.open(map, marker);

              if(oldinfowindow!=null)
              {
                   oldinfowindow.close();
              }

              oldinfowindow=infowindow;
         }));
      }
 function initialize() {
          var mapOptions = {
              center: new google.maps.LatLng(50.448807, -3.746826),
              zoom: 2
          };
          map = new google.maps.Map(document.getElementById("map_canvas"),mapOptions);

          loadmarkers();
      }

      function myTimer()
      {
          updatex();
      }

      var oldinfowindow=null;

      google.maps.event.addDomListener(window, 'load', initialize);

      var myVar=setInterval(function(){myTimer()},1000*60*5);

There is also one more helper file for converting country codes to names, but that is trivial and not shown here (but is included in the download)

Download the complete set of html/php/javascript/css files here:- http://static.byteme.org.uk/banhammer.tar.gz

There are 8 comments left Go To Comment

  1. Pingback: Deel Systems » Fail2ban et Google Maps /

  2. Pingback: Fail2ban et Google Maps | Deel Systems /

  3. Pingback: Populating mySQL with fail2ban information and banhammer | Neverending projects /

  4. Pingback: Visualizing your digital enemy /

  5. julien /

    I was thinking about doing something just like this and the updated failsql + this banhammer make it really easy.

    Note that there is bit of SQL missing in (GROUP BY and LIMIT 5):

    // Get count for each protocol
    $xx[‘protos’] = getdataset(“SELECT name,COUNT(name) as count FROM $table GROUP BY name”);

    // Get total counts for each country
    $xx[‘totals’] = getdataset(“SELECT country,COUNT(*) as count FROM $table GROUP BY country ORDER BY count DESC LIMIT 5”);

    It’s a bit sad to have port always = 0 because of the iptable-multiport feature of fail2ban but that’s OK.

    Julien

  6. Pingback: Fail2ban et Google Maps – Sortilege /

  7. Pingback: Fail2ban Ban Hammer for PHP7 /

  8. Pingback: Fail2ban + fail2sql + Ban Hammer + PHP7 | Rich Kreider /

Leave a Reply