View previous topic :: View next topic |
Author |
Message |
skakz Guru
Joined: 03 Jul 2004 Posts: 380 Location: Ischia/Napoli/Italia/Terra
|
Posted: Sun Jun 25, 2006 3:17 pm Post subject: [TOOL] come proteggersi dai brute forces (SSH, FTP, HTTP/s) |
|
|
l'idea, cosi' come lo stesso tool sono da attribuirsi a BlinkEye
Personalmente ho solo modificato leggermente il tool in modo da supportare anche il protocollo http/s
e una lista di ip da non bannare..
guardate il suo post e il wiki per avere un'idea di quello che permette di fare questo tool.
in sintesi questo programma gira in background e analizza periodicamente un file di log, attraverso dei pattern prestabiliti riconosce i tentavi di login falliti e, se questi superano una soglia massima, l'ip viene bannato temporaneamente.
per ulteriori informazioni leggetevi i link sopra.
l'unica cosa da fare è convogliare tutti i log che si vogliono analizzare in un unico file ma questo lo si può fare facilmente con syslog-ng.
personalmente ho inserito un pattern per il protocollo http che riguarda l'output di mod_security perchè è cio che io uso, però lo si può cambiare in ciò che si vuole.. (nel wiki ci sono molto esempi anche se non per http)
ecco il codice:
Code: | #!/usr/bin/python
# blacklist.py is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# blacklist.py is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# Original version by Reto Glauser aka blinkeye
# Mailto: blacklist at blinkeye dot ch
# Homepage: http://blinkeye.ch
# Forum post: http://forums.gentoo.org/viewtopic-t-421706.html
# Date: 2006-03-15
# Version 0.4.5
#
# Modified by Luca G. aka skakz [ lucag@lug-ischia.org ]
# Date: 25/06/2006
#
# NEW STUFF:
# added http/https support through mod_security log output
# (if you don't have mod_security you can still continue using this tool just editing HTTP_REGEX)
# added a check for allowed ips ( ALLOWEDIPS = "xxx.xxx.xxx.xxx yyy.yyy.yyy.yyy etc.etc."
#
# note: you must redirect all logs you want to scan in a file (LOG_INPUT). I do that symply using syslog-ng
import re;
import commands;
import thread;
import threading;
import sys;
from os import access, popen, R_OK, W_OK, X_OK;
from time import sleep, strftime, time;
from string import find;
from syslog import *;
import errno;
import os;
LOG_INPUT = "/var/log/blacklist.log"
LOG_OUTPUT = "/var/log/bannedips.log"
PID_FILE = "/var/run/blacklist.pid"
LOGTAIL = "/usr/sbin/logtail"
WHOAMI = "/usr/bin/whoami"
IPTABLES = "/sbin/iptables"
CUSTOM_CHAIN = "BLACKLIST"
PERMITTED_LOGIN_FAILURES = 6
BLOCKING_PERIOD = 600 #seconds
SUSPECTING_PERIOD = 600 #seconds
SLEEP_PERIOD = 30 #seconds
CHECK_INTERVALL = 300 #seconds
DATE_FORMAT = "%d.%m.%Y %X" # e.g.: 02.01.2006 23:49:12
SSH_PORT = 22
FTP_PORT = 21
HTTP_PORT = '80:443'
SENDMAIL = "/usr/sbin/sendmail"
MAIL_SENDER = "blacklist@omega.kzone"
MAIL_RECEIVER = "lucag@lug-ischia.org"
ALLOWEDIPS = ""
SSH_REGEX = [
r"sshd(?:.*) Authentication failure for (?P<user>.*) from (?:::ffff:)*(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})"
]
# SSH_REGEX catches following similar entries:
# Jan 2 21:48:05 blinkeye sshd[4529]: Failed password for invalid user sato from 61.172.192.3 port 54177 ssh2
# Jan 2 21:48:05 blinkeye sshd[4529]: Failed password for invalid user sato from ::ffff:61.172.192.3 port 54177 ssh2
# Oct 21 18:52:01 blinkeye sshd[31286]: Failed password for root from 152.149.148.115 port 36667 ssh2
# Oct 21 18:52:01 blinkeye sshd[31286]: Failed password for root from ::ffff:152.149.148.115 port 36667 ssh2
# Sep 18 05:08:06 blinkeye sshd[3971]: Failed keyboard-interactive/pam for root from 152.149.148.115 port 44896 ssh2
# Sep 18 05:08:06 blinkeye sshd[3971]: Failed keyboard-interactive/pam for root from ::ffff:152.149.148.115 port 44896 ssh2
FTP_REGEX = [
r"ftp(?:.*) authentication failure(?:.*) rhost=(?:::ffff:)*(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) user=(?P<user>.*)",
r"ftp(?:.*) authentication failure(?:.*) rhost=(?:::ffff:)*(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?P<user>)"
]
# FTP_REGEX catches following similar entries:
# Oct 3 19:35:41 blinkeye ftp(pam_unix)[8746]: authentication failure; logname= uid=0 euid=0 tty= ruser= rhost=206.222.29.194
# Oct 3 19:35:43 blinkeye ftp(pam_unix)[8746]: authentication failure; logname= uid=0 euid=0 tty= ruser= rhost=206.222.29.194 user=root
HTTP_REGEX = [
r"error(?:.*)client (?:::ffff:)*(?P<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\] mod_security(?:.*) \"(?: match )*(?P<user>.*)\"\]"
]
# Use
# grep Failed /var/log/auth.log | grep -o '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort | uniq -c
# to get a statistic of ip's from past login failures
# Use
# grep "Failed" /var/log/auth.log | sed "s/.*for\( invalid user\)*\(.*\)\(from.*\)/\2/" | sort | uniq -c
# to get a statistic of login names from past login failures
# Wrapper function for commands
def system_command( string_command ):
return_value = [ ]
return_value = ( commands.getstatusoutput( string_command ) )
if not return_value[ 0 ] == 0:
raise IOError( return_value[ 1 ] )
return return_value[ 1 ]
# block ip for the duration of time
def block( ip, time, port ):
try:
system_command( IPTABLES + " --new-chain " + CUSTOM_CHAIN )
system_command( IPTABLES + " --insert INPUT --jump " + CUSTOM_CHAIN ) # per inserire le regole dopo le mie
#system_command( IPTABLES + " --insert INPUT 11 --jump " + CUSTOM_CHAIN )
except:
None
# system_command( IPTABLES + " --insert " + CUSTOM_CHAIN + " --source " + ip + " --protocol tcp --dport " + str( port ) + " --jump REJECT" ) # cambio REJECT -> DROP
system_command( IPTABLES + " --insert " + CUSTOM_CHAIN + " --source " + ip + " --protocol tcp --dport " + str( port ) + " --jump DROP" )
LOG_OUTPUT.write( strftime( DATE_FORMAT ) + ": Blocking " + ip + " for " + str( time ) + " seconds\n" )
LOG_OUTPUT.flush()
# unblock IP
def unblock( ip, port ):
# system_command( IPTABLES + " --delete " + CUSTOM_CHAIN + " --source " + ip + " --protocol tcp --dport " + str( port ) + " --jump REJECT" ) # cambio REJECT -> DROP
system_command( IPTABLES + " --delete " + CUSTOM_CHAIN + " --source " + ip + " --protocol tcp --dport " + str( port ) + " --jump DROP" )
LOG_OUTPUT.write( strftime( DATE_FORMAT ) + ": Remove blocking " + ip + "\n" )
LOG_OUTPUT.flush()
# mail list of IP blocked during this run
def mail( mail_list ):
count = len( mail_list )
if count == 0:
return
ip = str( mail_list[ 0 ][ 0 ] ) + " on port " + str( mail_list[ 0 ][ 3 ] ) + "\n"
mail_list.remove( mail_list[ 0 ] )
p = popen( "%s -t" % SENDMAIL, "w" )
p.write( "From: " + MAIL_SENDER + "\n" )
p.write( "To: " + MAIL_RECEIVER + "\n" )
if count == 1:
p.write( "Subject: [" + system_command( 'hostname --long' ) + "]: Too many failures from " + ip + "\n\n" )
else:
p.write( "Subject: [" + system_command( 'hostname --long' ) + "]: Too many failures from multiple IPs\n\n" )
for entry in mail_list[ : ]:
ip += entry[ 0 ] + " on port " + str( entry[ 3 ] ) + "\n"
mail_list.remove( entry )
p.write( "Blocking out " + str( count ) + " IP(s):\n\n" + ip + "\n" )
if p.close() != None:
LOG_OUTPUT.write( strftime( DATE_FORMAT ) + ": Unable to send a mail. Check your SENDMAIL configuration.\n" )
LOG_OUTPUT.flush()
# append IPs from regex_matches to ip_list and increase counter of login failures for IPs
def create_stat( regex_matches, ip_list, ip_list_blocked, delay, port ):
current_time = time() + delay
for match in regex_matches:
entry = ip_list.get( match.group( 'ip' ) )
if entry == None:
ip_list[ match.group( 'ip' ) ] = [ match.group( 'ip' ), 1, current_time, port ] # [ [ ip ],[ counter for catched login failures ],[ time of last login failure ], [ port ] ]
entry = ip_list.get( match.group( 'ip' ) )
else:
entry[ 1 ] += 1
# no tolerance for a root login attempt
if ( match.group( 'user' ) == "root" ):
entry[ 1 ] += PERMITTED_LOGIN_FAILURES
for key in ip_list.keys()[ : ]:
if ip_list.get( key )[ 1 ] > PERMITTED_LOGIN_FAILURES and ip_list.get( key)[ 0 ] not in ip_list_blocked and ip_list.get( key)[ 0 ] not in ALLOWEDIPS:
ip_list_blocked.insert( 0, ip_list.get( key ) )
del ip_list[ key ]
block( ip_list_blocked[ 0 ][ 0 ], BLOCKING_PERIOD + delay, port )
mail_list.insert( 0, ip_list_blocked[ 0 ] )
# Someone must do the work
def scan():
global countdown
try:
new_log_entries = system_command( LOGTAIL + " -f " + LOG_INPUT )
except:
new_log_entries = system_command( LOGTAIL + " " + LOG_INPUT )
for i in range( 0, len( SSH_REGEX ) ):
re_ssh = re.compile( SSH_REGEX[ i ] )
regex_matches = re_ssh.finditer( new_log_entries )
create_stat( regex_matches, ssh_list, ssh_list_blocked, len( re_ssh.findall( new_log_entries ) )/100, SSH_PORT )
for i in range( 0, len( FTP_REGEX ) ):
re_ftp = re.compile( FTP_REGEX[ i ] )
regex_matches = re_ftp.finditer( new_log_entries )
create_stat( regex_matches, ftp_list, ftp_list_blocked, len( re_ftp.findall( new_log_entries ) )/100, FTP_PORT )
for i in range( 0, len( HTTP_REGEX ) ):
re_http = re.compile( HTTP_REGEX[ i ] )
regex_matches = re_http.finditer( new_log_entries )
create_stat( regex_matches, http_list, http_list_blocked, len( re_http.findall( new_log_entries ) )/100, HTTP_PORT )
# +++ mail section +++ #
mail( mail_list )
# +++ mail section +++ #
if countdown <= 0:
countdown = CHECK_INTERVALL
current_time = time()
for entry in ssh_list_blocked[ : ]:
if( current_time - entry[ 2 ] ) > BLOCKING_PERIOD:
unblock( entry[ 0 ], SSH_PORT )
ssh_list_blocked.remove( entry )
for key in ssh_list.keys():
if( current_time - ssh_list.get( key )[ 2 ] ) > SUSPECTING_PERIOD:
del ssh_list[ key ]
for entry in ftp_list_blocked[ : ]:
if( current_time - entry[ 2 ] ) > BLOCKING_PERIOD:
unblock( entry[ 0 ], FTP_PORT )
ftp_list_blocked.remove( entry )
for key in ftp_list.keys():
if( current_time - ftp_list.get( key )[ 2 ] ) > SUSPECTING_PERIOD:
del ftp_list[ key ]
for entry in http_list_blocked[ : ]:
if( current_time - entry[ 2 ] ) > BLOCKING_PERIOD:
unblock( entry[ 0 ], HTTP_PORT )
http_list_blocked.remove( entry )
for key in http_list.keys():
if( current_time - http_list.get( key )[ 2 ] ) > SUSPECTING_PERIOD:
del http_list[ key ]
# Check if there's another instance running
def handlepid():
try:
pidfile = os.fdopen( os.open( PID_FILE, os.O_WRONLY | os.O_CREAT | os.O_EXCL ), 'w' )
except OSError:
try:
pid = int( open( PID_FILE ).read() )
except IOError:
sys.exit( "Error opening pidfile %s" %PID_FILE )
try:
os.kill( pid, 0 )
except OSError, why:
if why.errno == errno.ESRCH:
print "Removing stale pidfile %s with pid %d\n" %( PID_FILE, pid )
os.remove( PID_FILE )
if os.path.exists( PID_FILE ):
sys.exit( "Cannot remove pidfile." )
else:
return handlepid()
sys.exit( "\nAnother blacklist daemon is running with pid %d" %pid )
pidfile.write( "%d\n" %os.getpid() )
pidfile.flush()
pidfile.close()
cleanup();
if not system_command( "whoami" ) == "root":
raise IOError, "This script must be run as root"
if not access( LOG_INPUT, R_OK ):
raise IOError, LOG_INPUT + " is not readable"
if not access( LOGTAIL, X_OK ):
raise IOError, LOGTAIL + " is not executable"
if not access( WHOAMI, X_OK ):
raise IOError, WHOAMI + " is not executable"
if not access( IPTABLES, X_OK ):
raise IOError, IPTABLES + " is not executable"
# +++ mail section +++ #
if not access( SENDMAIL, X_OK ):
raise IOError, SENDMAIL + " is not executable"
# +++ mail section +++ #
# test modus
if len( sys.argv ) == 2:
print( "\n* Entering test mode" )
for i in range( 0, len( SSH_REGEX ) ):
re_ssh = re.compile( SSH_REGEX[ i ] )
if not len( re_ssh.findall( sys.argv[ 1 ] ) ):
print( "* SSH_REGEX[ " + str( i ) + " ]: No match found" )
else:
regex_matches = re_ssh.finditer( sys.argv[ 1 ] )
for match in regex_matches:
print( "* SSH_REGEX[ " + str( i ) + " ]: Caught ip \"" + str( match.group( 'ip' ) ) + " and username \"" + str( match.group( 'user' ) ) + "\"" )
for i in range( 0, len( FTP_REGEX ) ):
re_ftp = re.compile( FTP_REGEX[ i ] )
if not len( re_ftp.findall( sys.argv[ 1 ] ) ):
print( "* FTP_REGEX[ " + str( i ) + " ]: No match found" )
else:
regex_matches = re_ftp.finditer( sys.argv[ 1 ] )
for match in regex_matches:
print( "* FTP_REGEX[ " + str( i ) + " ]: Caught ip \"" + str( match.group( 'ip' ) ) + "\" and username \"" + str( match.group( 'user' ) ) + "\"" )
for i in range( 0, len( HTTP_REGEX ) ):
re_http = re.compile( HTTP_REGEX[ i ] )
if not len( re_http.findall( sys.argv[ 1 ] ) ):
print( "* HTTP_REGEX[ " + str( i ) + " ]: No match found" )
else:
regex_matches = re_http.finditer( sys.argv[ 1 ] )
for match in regex_matches:
print( "* HTTP_REGEX[ " + str( i ) + " ]: Caught ip \"" + str( match.group( 'ip' ) ) + "\" and uri \"" + str( match.group( 'user' ) ) + "\"" )
p = popen( "%s -t" % SENDMAIL, "w" )
p.write( "From: " + MAIL_SENDER + "\n" )
p.write( "To: " + MAIL_RECEIVER + "\n" )
p.write( "Subject: blacklist.py ist testing your sendmail configuration\n\n" )
p.write( "A test mail from blacklist.py\n" )
if p.close() != None:
print( "* ERROR sending a mail. Check your SENDMAIL configuration.\n" )
else:
print( "* SUCCESS: Sending mail from " + MAIL_SENDER + " to " + MAIL_RECEIVER )
sys.exit(0)
# Cleanup
def cleanup():
try:
system_command( IPTABLES + " --delete INPUT -j " + CUSTOM_CHAIN )
system_command( IPTABLES + " --flush " + CUSTOM_CHAIN )
system_command( IPTABLES + " --delete-chain " + CUSTOM_CHAIN )
except:
None
# Call cleanup() before exiting the script
sys.exitfunc = cleanup
handlepid()
LOG_OUTPUT = file( LOG_OUTPUT, "a")
ssh_list = { }
ssh_list_blocked = [ ]
ftp_list = { }
ftp_list_blocked = [ ]
http_list = { }
http_list_blocked = [ ]
mail_list = [ ]
countdown = CHECK_INTERVALL
cleanup() # remove old entries
while 1:
scan()
sleep( SLEEP_PERIOD )
countdown -= SLEEP_PERIOD |
ecco invece il risultato:
Code: | omega ~ # ./blacklist.py "`cat /var/log/blacklist.log`"
* Entering test mode
* SSH_REGEX[ 0 ]: Caught ip "192.168.1.3 and username "root"
* SSH_REGEX[ 0 ]: Caught ip "192.168.1.3 and username "root"
* SSH_REGEX[ 0 ]: Caught ip "192.168.1.3 and username "sk"
* SSH_REGEX[ 0 ]: Caught ip "192.168.1.3 and username "illegal user dfsdgsd"
* FTP_REGEX[ 0 ]: Caught ip "127.0.0.1" and username "sk"
* FTP_REGEX[ 0 ]: Caught ip "127.0.0.1" and username "sk"
* FTP_REGEX[ 0 ]: Caught ip "127.0.0.1" and username "sk"
* FTP_REGEX[ 1 ]: Caught ip "127.0.0.1" and username ""
* FTP_REGEX[ 1 ]: Caught ip "127.0.0.1" and username ""
* FTP_REGEX[ 1 ]: Caught ip "127.0.0.1" and username ""
* FTP_REGEX[ 1 ]: Caught ip "127.0.0.1" and username ""
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/"
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/"
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/sms"
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/sms/"
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/sms/php"
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/sms/php/index.php"
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/"
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/"
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/index.php"
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/private/"
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/ftp/"
* HTTP_REGEX[ 0 ]: Caught ip "219.144.196.226" and uri "/ss/"
* SUCCESS: Sending mail from blacklist@omega.kzone to lucag@lug-ischia.org |
Code: | omega log # cat bannedips.log
omega log # cat bannedips.log
25.06.2006 16:54:32: Blocking 192.168.1.3 for 600 seconds
25.06.2006 16:54:32: Blocking 127.0.0.1 for 600 seconds
25.06.2006 16:54:32: Blocking 219.144.196.226 for 600 seconds
25.06.2006 17:04:32: Remove blocking 192.168.1.3
25.06.2006 17:04:32: Remove blocking 127.0.0.1
25.06.2006 17:04:32: Remove blocking 219.144.196.226
omega log # |
l'email che invia:
Code: | subject: [omega.kzone]: Too many failures from multiple IPs
Blocking out 3 IP(s):
219.144.196.226 on port 80:443
127.0.0.1 on port 21
192.168.1.3 on port 22
|
personalmente la trovo una soluzione puù che ottima. _________________ Linux Registered User n.340423
Linux User Group Ischia
www.tush.it
Last edited by skakz on Mon Jun 26, 2006 8:10 am; edited 1 time in total |
|
Back to top |
|
|
.:chrome:. Advocate
Joined: 19 Feb 2005 Posts: 4588 Location: Brescia, Italy
|
Posted: Sun Jun 25, 2006 3:34 pm Post subject: Re: [TOOL] come proteggersi dai brute forces (SSH, FTP, HTTP |
|
|
personalmente ho usato per diversi mesi denyhosts, che fa lo stesso lavoro per il solo SSH
devo dire che sebbene possa sembrare una buona soluzione, alla lunga da un saccod i problemi, specie su macchine molto esposte, o in reti "di test".
questi tool diventano un'arma che puntualmente si rivolge sempre contro l'amministratore
molto meglio configurare in modo adeguato i servizi. comporta meno problemi, ed è comunque una cosa da fare, invece che lasciare le configurazioni standard come fanno tutti |
|
Back to top |
|
|
makoomba Bodhisattva
Joined: 03 Jun 2004 Posts: 1856
|
Posted: Sun Jun 25, 2006 6:29 pm Post subject: |
|
|
lo script può risultare utile in determinate situazioni, quindi meglio averlo.
personalmente, concordo con k.gothmog: le soluzioni "attive" possono essere pericolose e tendono a finire nel .... del sysadmin.
un pò come le policy a drop di iptables: te ne scordi, dai un flush e dopo 3 secondi partono i bestemmioni. _________________ When all else fails, read the instructions. |
|
Back to top |
|
|
skakz Guru
Joined: 03 Jul 2004 Posts: 380 Location: Ischia/Napoli/Italia/Terra
|
Posted: Sun Jun 25, 2006 8:57 pm Post subject: |
|
|
l'intento di questo script non è certo quello di risolvere problemi di sicurezza. e come dice appunto k.gothmog meglio "configurare bene i servizi".
io riporto la mia esperienza personale: è uno tool affidabile leggero ed economico in termini di risorse.. non si "ingolfa" con il passare del tempo nè con server molto trafficati.. leggete qui!! personalmente ho un firewall molto complesso con molte regole e anche con l'aggiunta di questo tool devo dire che il sistema non ne risente minimamente. Utilissimo a chi come me deve poter accedere al server dai posti più svariati e vuole avere una soluzione unica che glielo consenta. (senza chiavi knock o altre cose del genere)
vedetela come una "aggiunta".. giusto per levarvi dai piedi quei fastidiosissimi tentativi di intrusione con password ridicole che vi fanno solo kilometri di log da dover controllare... _________________ Linux Registered User n.340423
Linux User Group Ischia
www.tush.it |
|
Back to top |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|