Блокировка сканеров безопасности и визуализация Fail2Ban в Grafana

Как правило, сканеры безопасности не представляют угрозы для сайта, если конечно вы следите за его состоянием. Но активность сканеров может быть сигналом, да и ничего приятного нет когда копаются в ваших вещах. В данной заметке я рассмотрю несколько методов блокировки сканеров и создание графиков состояния фильтров fail2ban


Самый простой метод, но и самый недейственный - это блокировка User-Agent. Пример для Nginx:
if ($http_user_agent ~* ZmEu|nessus|libwhisker) {
            return 403;
     }
Второй метод, на мой взгляд, самый эффективный - организация honeypot. Вот хороший пример реализации этого метода - "How to Block Automated Scanners from Scanning your Site" 

И еще один метод - это блокировка источников, которые используют шаблоны для сканирования сайтов. Такие сканеры определяются довольно легко - по всплеску ошибок 404 и 403 в логах сервера. Для блокировки создаются фильтры в fail2ban. Я создал отдельные фильтры для 404 и 403 ошибок:
#cat /etc/fail2ban/filter.d/nginx-403.conf

# fail2ban filter configuration for nginx
[Definition]
failregex = ^<HOST> .* "(GET|POST) [^"]+" 403
ignoreregex =

#/etc/fail2ban/filter.d/nginx-404.conf
# fail2ban filter configuration for nginx 
[Definition] 
failregex = ^<HOST> .* "(GET|POST) [^"]+" 404
ignoreregex =

Включаем фильтры в fail2ban, добавляем в  /etc/fail2ban/jail.conf

[nginx-404]  

enabled = true
filter  = nginx-404
port    = http,https
logpath = /var/log/nginx/access.log
bantime  = 86400  ; 1 day
findtime = 600   ; 10 min
maxretry = 30                                                                                                                                                                                    
[nginx-403]
                                           
enabled = true
filter  = nginx-403
port    = http,https
logpath = /var/log/nginx/access.log
bantime  = 86400  ; 1 day
findtime = 600   ; 10 min
maxretry = 30

Логика блокировки проста - при превышении порога количества ошибок в течении 10 минут, адрес блокируется.

Перейдем к визуализации и оповещению.
Следующим этапом организовываем графики количества блокировок. Для чего я использовал logster и graphite.
Устанавливаем logster:
# apt-get install logster
Для того что бы разобрать лог-файл fail2ban, я написал парсер Fail2Ban.py: 

###<https://nklug.org.ua>
###

import time
import re
import os.path


from logster.logster_helper import MetricObject, LogsterParser
from logster.logster_helper import LogsterParsingException

class Fail2Ban(LogsterParser):
    
   def __init__(self, option_string=None):
          '''Initialize any data structures or variables needed for keeping track
          of the tasty bits we find in the log we are parsing.'''
          self.statsban = 0
          self.statsunban = 0
           
          self.reg = re.compile('.*(?P<ban>(Ban|Unban)).*')
    
   def parse_line(self, line):
       '''This function should digest the contents of one line at a time, updating
       object's state variables. Takes a single argument, the line to be parsed.'''
        
       try:
           regMatch = self.reg.match(line)
            
           if regMatch:
               linebits = regMatch.groupdict()
               if (linebits['ban'] == 'Ban'):
                   self.statsban += 1
               elif (linebits['ban'] == 'Unban'):
                   self.statsunban += 1
            
           else:
               raise LogsterParsingException, "regmatch failed to match"

       except Exception, e:
           raise LogsterParsingException, "regmatch or contents failed with '%s'" % e

   def get_state(self, duration):
       # Return a list of metrics objects
       if os.path.isfile('/tmp/ban.txt'):
            fileban = open( '/tmp/ban.txt')
            oldban = int(fileban.read())
       else:
            oldban = 0
       currentban = oldban+(self.statsban-self.statsunban)
       fileban = open( '/tmp/ban.txt', 'w+' )
       fileban.write(str(currentban))
       fileban.close
       return [
           MetricObject("Ban", (self.statsban), "IP"),
           MetricObject("Unban", (self.statsunban), "IP"),
           MetricObject("CurrentBan", (currentban), "IP"),
       ]
 

Парсер помещаем в директорию Logster - /usr/lib/python2.7/dist-packages/logster/parsers/ 
И добавляем выполнение этого скрипта с интервалом в 30 сек в crontab:
* * * * * root /usr/bin/logster -o graphite -p logster --graphite-host=10.0.0.1:2003 Fail2Ban /var/log/fail2ban.log
* * * * * root (sleep 30 ; /usr/bin/logster -o graphite -p logster --graphite-host=10.0.0.1:2003 Fail2Ban /var/log/fail2ban.log)
результат направляем в graphite. В результате получаем следующий график:

fail2ban grafana

Для отображения актуального числа заблокированных адресов, после первого старта необходимо установить текущее количество заблокированных в файл /tmp/ban.txt . Например так:
#iptables-save |grep fail2|grep "\-s "|wc -l > /tmp/ban.txt
Вот собственно пока и все