Introduction

Some projects are born out of technical necessity, others from curiosity and the desire to experiment.
Bannator.py was created for all these reasons.

It was April 2, 2010. I was working as a consultant for a company that was experiencing repeated unauthorized access attempts on its servers: classic SSH password brute force attacks. A quick, simple, and understandable solution was needed. I could have written a couple of lines in Bash and solved the problem in a few minutes… but this was also an opportunity: to turn a technical need into a learning moment.

The company’s technical team had shown interest in Python, considering it more “readable” and “user-friendly” compared to Bash scripts and other sysadmin-oriented solutions. So, I decided to create a small script of just 44 lines, commented line by line, so it would not only be a tool but also a practical example of system automation in Python.

The result was Bannator.py – named because it’s the terminator of annoying IPs.

A script that:

  • Reads the authentication logs (/var/log/auth.log)
  • Identifies IPs with multiple failed login attempts
  • Counts failed attempts
  • Automatically blocks persistent IPs by writing them into /etc/hosts.deny
  • Creates a backup of the block file, so no rules are lost

I also created a YouTube video tutorial showing the script in action on a VirtualBox machine running Debian (version 5.0 Lenny). It was a way to combine teaching and real-world practice, demonstrating how a security issue can be solved with a simple “homegrown” but effective approach.

Why bring it back today?

Today is August 14, 2025. More than 15 years after its first publication, Bannator.py is obviously outdated (…it already was) compared to modern integrated security solutions, but it still remains:

  • A perfect example for anyone who wants to learn how to read and write Python scripts
  • A practical exercise in log parsing
  • A small piece of personal history

Full Code

  • Original code dated April 2, 2010
  • Republished on an old website on February 23, 2023
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Autore: Vincenzo Argese @crisiumdevstudio
# YouTube: https://www.youtube.com/@crisiumdevstudio <-- Iscriviti e attiva la campanella GRAZIE
#
# Script: bannator.py
# Descrizione: Script Python – bloccare IP da attacchi brute force
# Data pubblicazione: 2 Aprile 2010 (Data di oggi 23 feb 2023 ...OMG)
#
# Web: www.vasystems.it
# Articolo: https://www.informaticawebsystems.com/guida-script-python-bloccare-indirizzi-ip-attacchi-brute-force/
# (Ex-Progetto) Progetto Elven.it WebTV: https://www.youtube.com/channel/UCf36btbKZPx8wTvIP6jKPaQ


# Importiamo la libreria string per gestire le righe dei file
import string

# In questa variabile inseriamo il percorso del file di log
pathlog = '/var/log/'

# Indichiamo in questa variabile il file di log su da verificare
filelog = 'auth.log'

# Specifichiamo il path file hosts.deny
pathban = '/etc/'
#
fileban = 'hosts.deny'

# Nella variabile target specifichiamo la stringa da utilizzare nella ricerca degli indirizzi IP
# che stanno tentando l'accesso nel nostro sistema. Sappiamo che i log sono presenti nel
# file /var/log/auth.log e la riga del log con l'accesso fallito e'
# Failed password for invalid user admin from 192.168.1.2 port 17727 ssh2
# Quindi prendiamo come riferimento la stringa 'Failed password'
#
target = 'Failed password'

# Numero di tentativi massimi consentiti da ogni IP per connettersi al server
# su cui e' in esecuzione questo script.
#
BANNA = 3

# Iniziamo a realizzare il codice vero e proprio:

# Contiamo il numero di caratteri della stringa contenuta in target, questo numero ci
# permettera' di gestire la stringa e identificare in modo assolutamente preciso l'IP da bloccare.
#
lunghezza = len(target)

# Apro il file auth.log in sola lettura (perche' devo semplicemente ricavare l'informazione
# sulle connessioni e sugli IP)
#
f = file(pathlog+filelog, 'r')

# Creiamo un dizionario python contenente gli IP bannati.
# Il dizionario e' una struttura che ci permette di gestire le informazioni per chiave e valore
# nel nostro caso useremo come chiave l'IP e come valore un numero intero che conta le volte
# che l'IP non riesce ad eseguire l'accesso.
# {key:valore, key2:valore2, …}
# es.: {'192.168.1.2': 2, '172.16.8.10': 3}
#
ip_bannati = {}

# Leggo la prima riga del file auth.log e copio il contenuto nella variabile i;
i = f.readline()

# Ciclo il file finche' la variabile i che contiene la riga da esaminare non e' vuota
# quindi finche' il file auth.log non e' terminato.
#
while (i != ""):
    # Tramite il metodo find di string ottengo la posizione della stringa in cui stato trovato il target
    #
    posizione = string.find(i,target)

    # Se la posizione e' diversa da -1 significa che il targhet e' stato trovato
    # Quindi la stringa letta contiene 'Failed password' ora dobbiamo riuscire a prendere
    # la parte della stringa che contiene l'IP
    if posizione != -1:
        # RICERCA IP
        # Concentriamoci sulla riga del file auth.log e in particolare alle parole tra cui l'IP
        # e' compreso: from 192.168.1.2 port
        #
        # Prima di identificare l'IP faccio la ricerca della stringa
        # from per assicurarmi la corretta posizione
        #
        inizio = string.find(i,"from")
        if inizio != -1 :
            inizio = inizio + 5 # +5 caratteri di from e spazio

            # Calcolo la posizione finale tramite la stringa port
            fine = string.find(i,"port")

            # Ottengo la substring con con l'IP da bannare
            ip = i[inizio:fine-1]

            # Se l'IP e' gia' presente in ip_bannati
            if ip in ip_bannati:
                # Aggiorna semplicemente il suo contatore
                ip_bannati[ip] = ip_bannati[ip] + 1
            else :
                # Altrimenti inserisci l'IP e imposta il suo contatore a 1
                ip_bannati[ip] = 1

        # A questo punto abbiamo finito il lavoro su una singola riga quindi procediamo
        # con la riga successiva del file auth.log e ed esaminiamola nuovamente con il ciclo while
        i = f.readline()

# In questo punto siamo usciti dal while, questo significa che il file auth.log e' terminato
# e non abbiamo altre righe da esaminare, quindi possiamo chiudere il file.
f.close()

# Creo una copia di backup del file hosts.deny perche' ogni volta che lancio lo script
# perdo gli IP bloccati precedentemente
denyold = file(pathban+fileban, 'r') # Apro hosts.deny in lettura
deny = file('_'+fileban, 'w') # Creo il file _hosts.deny di backup

# Leggo tutte le righe di hosts.deny e le copio in _hosts.deny
i = denyold.readline()
while (i!=""):
    deny.write(i)
    i = denyold.readline()

# Ora non resta che verificare quali IP hanno superato il numero
# massimo di tentativi consentiti e scriverli nel file _hosts.deny con la regola opportuna.
#
# Ciclo per verificare tutti gli IP inseriti nel dizionario
#
for i in ip_bannati :
    # Se l'IP ha superato il numero massimo di tentativi
    #
    if ip_bannati[i] > BANNA :
        # Scrivi nel file hosts.deny l'IP con la regola ALL = BLOCCA TUTTO
        # es.: ALL:192.168.1.10 e vai a capo.
        #
        deny.write('ALL:'+ i+'\n')

# Chiudo i file
denyold.close()
deny.close()

# Copio il contenuto di _hosts.deny nel file /etc/hosts.deny
deny = file(pathban+fileban, 'w')
denytmp = file('_'+fileban, 'r')
i = denytmp.readline()
while (i!=""):
    deny.write(i)
    i = denytmp.readline()

denytmp.close()
deny.close()
# Fine