Guida Script Python bloccare indirizzi IP attacchi brute force
Questo primo articolo è stato ripreso dalla rubrica dedicata all’informatica e alla tecnologia del Progetto Elven TV.
Vogliamo realizzare uno script molto semplice e ideale a livello didattico per far comprendere la potenza del linguaggio di programmazione Python, la struttura dei log e alcuni file di configurazione utili di linux.
Questo articolo comprende anche una VideoGuida dove vengono spiegate le operazioni da fare e ogni riga di codice dello script.
Questo programma in python, permette di bloccare o bannare gli indirizzi IP dei computers che tentano di accedere al nostro sistema tramite la tecnica del brute force o a forza bruta.
Un attacco di tipo brute force si verifica quando un utente ma molto più spesso un software, tenta in modo stupido e metodico, la connessione su un server provando e riprovando combinazioni di password e username finché non viene trovata la combinazione corretta per l’accesso.
Qualche premessa prima di vedere il codice sorgente, mi sembra dovuta, questo script non vuole essere una soluzione completa al problema e di sicuro non è la migliore, ma solo una soluzione didattica funzionante e funzionale.
Con questo piccolo programma in python non facciamo altro che copiare testo tra due semplici file, averlo applicato ad un problema sistemistico reale rende, semplicemente, la guida più divertente.
Voglio sottolineare che il problema della sicurezza informatica è un qualcosa di molto più complesso e non può essere risolto con una semplice guida e qualche riga di codice e chi ha provato a studiare questa materia, penso sia arrivato alla conclusione che per quanto si voglia rendere sicuro un sistema non si potrà mai farlo al 100% (le percentuali sono molto ma MOLTO più basse).
Sicuramente possiamo, successivamente, migliorare il programma aggiungendo altre funzionalità come ad esempio gestire indirizzi IP uguali, questo programma infatti non si preoccupa di verificare se in hosts.deny è già presente un IP che stiamo bloccando, la ricerca degli IP è fatta tramite il metodo per la libreria string di python, qualcuno potrebbe pensare di modificarlo usando le espressioni regolari (regular expression), lo script potrebbe essere realizzato usando lo shell scripting, si potrebbe realizzare una comoda interfaccia di gestione, dare la possibilità di poter settare tramite un file di configurazione i file log da controllare e le informazioni da gestire e mille altre features mi vengono in mente mentre scrivo questa guida, nulla toglie che se prendete questo programma e lo usate, fa semplicemente quello che è stato programmato per fare… bloccare gli IP.
Detto questo, passiamo al nostro lavoro, lo script si occuperà di identificare i tentativi di connessioni non riuscite sul nostro server andando a verificare il file /var/log/auth.log per ottenere l’IP dalla macchina ultima che l’ “Hacker” sta usando per eseguire l’ “attacco” e inserirlo nel nel file /etc/hosts.deny bloccando l’accesso al nostro server dall’IP indicato.
Prerequisiti:
Per meglio comprendere quello che andiamo a realizzare, bisognerebbe conoscere qualche comando base di linux, la struttura delle directory e dei file e aver studiato un qualsiasi linguaggio di programmazione anche in modo molto elementare.
Altro prerequisito importante è avere a disposizione due computer per effettuare i test o in alternativa utilizzare e conoscere un sistema di virtualizzazione come VirtualBox.
Andiamo nel dettaglio.
Python è un linguaggio di programmazione ad alto livello interpretato e permette di realizzare codice sorgente in molti paradigmi di programmazione, ma la cosa importante da sapere è che in Python tutto è un oggetto (OOP – Object-oriented programming – Programmazione Orientata agli Oggetti).
Con questo linguaggio possiamo realizzare applicazioni complesse con o senza interfaccia grafica (GUI), multi piattaforma, applicazioni Web e semplici o complessi script di automazione.
Senza ombra di dubbio Python è un grande alleato di ogni sistemista.
Il sistema operativo su cui andiamo a lavorare e per cui realizzeremo il nostro script didattico è linux. Potete scegliere la distribuzione che preferite, personalmente scelgo Debian e derivate ma con poche modifiche il programma può essere adattato su ogni sistema unix like.
I due file su cui andiamo a lavorare sono:
/var/log/auth.log
è uno dei file log di sistema, contiene informazioni che hanno un valore inestimabili per chiunque lavori con linux e in particolare contiene i log di accesso al sistema.
Un file di log è un semplice file di testo su cui il sistema scrive tutto quello che avviene nel sistema stesso, ne esistono tantissimi quello su cui si basa la nostra guida contiene le informazioni relative alle connessioni e agli accessi.
/etc/hosts.deny contiene le regole per negare l’accesso a tutti i client.
Il nostro script non dovrà fare altro che leggere il file /var/log/auth.log verificare se gli IP che stanno tentando l’accesso al nostro server falliscano per un certo numero di volte prestabilito, prendere l’IP e scriverlo nel file /etc/hosts.deny applicando la regola per bloccare completamente l’IP.
Ambiente di lavoro:
Per testare il nostro script utilizzeremo due computer con sistema operativo linux e in particolare un client da cui tenteremo di eseguire le connessioni e un sistema operativo che fungerà da server che riceverà le connessioni dal client.
Per realizzare questo piccolo sistema sono necessari due computer collegati in rete o come si è preferito in questa guida, il server sarà virtualizzato tramite VirtualBox opportunamente configurato per fare in modo che la macchina virtuale risulti presente nella rete del sistema desktop su cui viene eseguito mentre il client sarà il pc desktop su cui è installato VirtualBox.
Riepilogando:
Client :
Computer FISICO desktop;
Sistema Operativo Ubuntu 9.10;
IP 192.168.1.2
Server:
Macchina virtuale (VirtualBox)
Sistema Operativo Debian GNU Linux 5.0 Lenny
IP 192.168.1.150
Nome utente: vincenzo
File di log: /var/log/auth.log
File gestione host: /etc/hosts.deny
Per funzionare in modo corretto avete bisogno sul server di almeno un editor di testo e l’interprete python, in questo caso come editor di testo ho scelto vim, voi potete usare quello che preferite.
Python e Vim dovrebbero essere già presenti se state usando Debian ma per scrupolo lanciamo il comando per l’installazione di questi due pacchetti.
PROCEDIAMO:
1) Testare il corretto funzionamento del sistema prima di eseguire lo script
Eseguire la connessione dal client sul server tramite il comando ssh
ssh nome utente@host
Esempio:
vincenzo@desktop:~/tmp$ ssh vincenzo@192.168.1.150
vincenzo@192.168.1.150’s password:
Linux elvenit 2.6.26-2-686 #1 SMP Wed Feb 10 08:59:21 UTC 2010 i686
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
No mail.
Last login: Wed Mar 10 16:49:47 2010
Se tutto avviene in modo corretto come da esempio, disconnettersi dal server con il comando exit
logout
Connection to 192.168.1.150 closed.
vincenzo@desktop:~/tmp$
2) Eseguire almeno 3 tentativi di connessione sbagliando volutamente password o username come da esempio:
vincenzo@192.168.1.150’s password:
Permission denied, please try again.
vincenzo@192.168.1.150’s password:
Permission denied, please try again.
vincenzo@192.168.1.150’s password:
Permission denied (publickey,password).
vincenzo@desktop:~/tmp$
A questo punto siamo sicuri che nel file log di sistema /var/log/auth.log sono presenti 3 tentativi di connessioni non riusciti, possiamo verificarlo collegandoci sul nostro server e guardando le ultime righe del file auth.log con il comando tail:
da root o con il comando sudo lanciare:
~# tail /var/log/auth.log
… righe del file di log…
Mar 10 19:44:04 elvenit sshd[2345]: Failed password for vincenzo from 192.168.1.2 port 50967 ssh2
Mar 10 19:44:07 elvenit sshd[2345]: Failed password for vincenzo from 192.168.1.2 port 50967 ssh2
… righe del file di log…
Queste che vedete in alto sono le righe del file di log che ci interessano e ci dicono: giorno data e ora, host, il protocollo, il messaggio loggato, l’ip del client che ha tentato la connessione e la porta.
Ok, la parte di queste righe che ci interessa è senza dubbio l’ip del client, nell’esempio 192.168.1.2 e la frase “Failed password” che useremo per individuare i tentativi di connessioni andati male.
3) A questo punto non resta che studiare il codice del nostro script ed eseguirlo:
Nella stesura del codice in questa guida farò ampio uso dei commenti.
Codice sorgente python per script blocca IP:
Fate il copia incolla del sorgente e salvatelo in un file con nome bannator.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#——————————————————
# www.elven.it
# Script Python – bloccare IP da attacchi brute force
# file: bannator.py
# Vincenzo Argese info@elven.it
#——————————————————
# 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
4) Rendiamo eseguibile
5) Eseguiamo lo script (sono necessari i permessi di root)
E’ chiaro che lo script deve essere lanciato con i permessi da superuser root, altrimenti non riesce ad eseguire le operazioni di scrittura su hosts.deny.
7 ) Se tutto è andato bene dobbiamo trovare nella directory del nostro script il file _hosts.deny di backup e se facciamo un cat del file /etc/hosts.deny deve contenere gli IP bloccati.
ALL:192.168.1.2
vincenzo@elvenit:~/tmp$
A questo punto si potrebbe schedulare il nostro script con crontab in modo da automatizzare la sua esecuzione in intervalli di tempo prestabiliti ma questa è un’altra storia.
SE STATE LAVORANDO IN REMOTO RISCHIATE DI BANNARVI DA SOLI 😉
DOWNLOAD SCRIPT
Nel caso in cui il copia incolla del codice generi qualche errore, potete scaricare il sorgente dello script bannator.py.
Una volta fatto il download del file bannator.txt, rinominare in bannator.py (cambiando semplicemente l’estensione da .txt a .py) ed eseguite i test.
Buon divertimento e continuate a seguirci,
Vincenzo Argese (vaSystems)
Vincenzo salve!
Ho copiaincollato il codice (possiedo una VPS con Debian7).
Ho dovuto correggere qualche piccola differenza di sintassi ( e cioè mettere ‘ al posto di ‘ rovesciata – che qui non riesco a scrivere – e ” al posto di ” rovesciate – idem + ho dovuto indentare le istruzioni che seguono i due punti).
Insomma poche correzioni.
Il fatto è che il comando ./nomefile esegue un loop infinito…
Hai qualche idea in proposito?
Grazie per la risposta, magari qui di seguito, meglio se (anche) alla casella che ho indicato sopra.
Marco
Pardon, dimenticavo…
I path e tutto il resto mi pare a posto.
Buongiorno,
Questo articolo ormai ha qualche anno e sono sicuro che il codice va aggiornato e se ne possono scrivere di più efficienti.
In questi giorni faccio qualche test e risponderò sia in privato sia qui per tutti gli utenti.
Grazie,
Vincenzo
Gentilissimo Vincenzo,
come d’accordo pubblico qui di seguito il codice (che rispetto all’originale ho corretto per il mio ambiente: Debian 7, Python 2.7 ed altro, modificando le virgolette e le indentazioni).
Sono sicuro che (almeno) le indentazioni (perlomeno intrinsecamente) siano a posto.
I percorsi sono a posto.
Forse qualche indentazione non va bene (crea blocchi che non dovrebbero esistere) ?
Sono ammirato per la tua disponibilità e non dubito saprai darmi qualche dritta per mettere a posto il tutto, che senz’altro dipende da qualche banalità (ma hw e sw non giudicano > agiscono).
Grazie sentitamente sin d’ora e una volta di più per l’aiuto.
Marco
************************************************************************
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
import string
pathlog = ‘/var/log/’
filelog = ‘auth.log’
pathban = ‘/etc/’
fileban = ‘hosts.deny’
target = ‘Failed password’
BAN = 3
lenght = len(target)
f = file (pathlog + filelog, ‘r’)
banned_ip = {}
i = f.readline ()
while (i!= “”) :
position = string.find (i, target)
if position != -1 :
start = string.find (i, ” from “)
if start != -1 :
start = start + 5
end = string.find (i, ” port “)
ip = i [start : end – 1]
if ip in banned_ip :
banned_ip [ip] = banned_ip [ip] + 1
else :
banned_ip [ip] = 1
i = f.readline ()
f.close ()
denyold = file (pathban + fileban, ‘r’)
deny = file (‘_’ + fileban, ‘w’)
i = denyold.readline ()
while (i!= “”) :
deny.write (i)
i = denyold.readline ()
for i in banned_ip :
if banned_ip [i] > BAN :
deny.write (‘ALL:’ + i + ‘\n’)
denyold.close ()
deny.close ()
deny = file (pathban + fileban, ‘w’)
denytmp = file (‘_’ + fileban, ‘r’)
i = denytmp.readline ()
while (i!= “”) :
deny.write (i)
i = denytmp.readline ()
denytmp.close ()
deny.close ()
Perbacco!
Nel post NON si vedono le indentazioni…
Come facciamo? 🙂
Marco
Gentilissimo Vincenzo,
come d’accordo pubblico qui di seguito il codice (che rispetto all’originale ho corretto per il mio ambiente: Debian 7, Python 2.7 ed altro, modificando le virgolette e le indentazioni).
Sono sicuro che (almeno) le indentazioni (perlomeno intrinsecamente) siano a posto.
I percorsi sono a posto.
Forse qualche indentazione non va bene (crea blocchi che non dovrebbero esistere) ?
Sono ammirato per la tua disponibilità e non dubito saprai darmi qualche dritta per mettere a posto il tutto, che senz’altro dipende da qualche banalità (ma hw e sw non giudicano > agiscono).
Grazie sentitamente sin d’ora e una volta di più per l’aiuto.
Marco
Perbacco!
Nel post NON si vedono le indentazioni…
Come facciamo?
Proviamo così: fra parentesi indico le indentazioni con un numero (= spazi battuti sulla tastiera). Ove il numero non è indicato l’indentazione è = 0
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
import string
pathlog = ‘/var/log/’
filelog = ‘auth.log’
pathban = ‘/etc/’
fileban = ‘hosts.deny’
target = ‘Failed password’
BAN = 3
lenght = len(target)
f = file (pathlog + filelog, ‘r’)
banned_ip = {}
i = f.readline ()
while (i!= “”) :
(3) position = string.find (i, target)
(3) if position != -1 :
(6) start = string.find (i, ” from “)
(6) if start != -1 :
(9) start = start + 5
(9) end = string.find (i, ” port “)
(9) ip = i [start : end – 1]
(9) if ip in banned_ip :
(12) banned_ip [ip] = banned_ip [ip] + 1
(9) else :
(12) banned_ip [ip] = 1
i = f.readline ()
f.close ()
denyold = file (pathban + fileban, ‘r’)
deny = file (‘_’ + fileban, ‘w’)
i = denyold.readline ()
while (i!= “”) :
(3) deny.write (i)
(3) i = denyold.readline ()
for i in banned_ip :
(3) if banned_ip [i] > BAN :
(6) deny.write (‘ALL:’ + i + ‘\n’)
denyold.close ()
deny.close ()
deny = file (pathban + fileban, ‘w’)
denytmp = file (‘_’ + fileban, ‘r’)
i = denytmp.readline ()
while (i!= “”) :
(3) deny.write (i)
(3) i = denytmp.readline ()
denytmp.close ()
deny.close ()
Dopo aver lanciato python nomefile.py il file lavora, ma si pianta in un loop infinito…
Questo il messaggio di errore (di orrore?) che ricevo dopo aver fermato l’esecuzione con ^C:
^CTraceback (most recent call last):
File “totalban.py”, line 29, in
position = string.find (i, target)
File “/usr/lib/python2.7/string.py”, line 361, in find
return s.find(*args)
KeyboardInterrupt