Como saber las IPs que han realizado más de N peticiones en S segundos a nuestro servicio Apache

por | febrero 23, 2014

Tras una pregunta de un alumno en clase sobre control y detección de peticiones abusivas, recordé el guión que desarrollé, cómo no con Python ;), para detectar «ráfagas» de conexiones. Con Python es muy fácil analizar los logs del servidor Apache y consultar qué IPs nos han realizado más de X peticiones (entendiendo por X un parámetro que introducimos como argumento) en un número de segundos determinado (también introducido como argumento del guión)

Dependiendo de qué información queramos extraer, ajustaremos el número de peticiones y segundos. Por ejemplo, ante subidas en la carga del servidor, podemos comprobar qué IPs cumplen un patrón y, si consideramos que se están realizando con mala fe, bloquear el tráfico proveniente de estas IPs.

Aunque en EPSAlicante rotamos los logs, por si no se hace o por si la información que queremos analizar está en un periodo de tiempo determinado, esto también puede indicarsele al guión (D días, H horas y M minutos antes).

El desarrollo de este guión apenas me llevó 2 horas (y con interrupciones) y, sin embargo creo que es muy útil. Podemos hasta ejecutarlo periódicamente para ver qué obtenemos. ¡Nunca se sabe las sorpresas que nos podemos llevar!

Creo que incluso sería bueno (posible mejora) que la información la proporcionara en XML para integrarlo con otros sistemas de administración y/o seguridad.

Justo debajo de este párrafo está el guión. La función más importante es readHistoryIP que devuelve un diccionario con clave IP y valor otro diccionario cuya clave es la fecha de registro del evento y el valor el número de veces que para esa IP aparece un evento +/- segundos con respecto a la fecha del evento. En el cuerpo principal se muestra la información de las IPs que aparecen más veces que del valor indicado por parámetro para el intervalo de tiempo establecido.

#!/usr/bin/python
#encoding:utf-8
try:
 import sys,optparse, datetime,re
except:
 print("Error running 'import sys,optparse,datetime,re'. Maybe you have to install some python library")

def isValid(ip):
 if (re.match("^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$",ip)):
 return True
 elif re.match(r'^[a-zA-Z0-9_]{,63}(.[a-zA-Z0-9_]{,63}){3}$',ip): #("^W+(.W)$", ip):
 return True
 else:
 #print "Name/IP host wrong. You have to write a FQDN name or a IP direction"
 return False
 return False

def readHistoryIP(logfile,sec, deltaday,deltahour,deltaminutes):
 months = {"Ene":1, "Feb":2, "Mar":3, "Abr":4, "May":5, "Jun":6, "Jul":7, "Ago":8, "Sept":9, "Oct":10, "Nov":11, "Dic":12}
 today = datetime.datetime.now()
 refDay= datetime.timedelta(days=int(deltaday), hours=int(deltahour), minutes=int(deltaminutes))

 ipHitListing = { }
 try:
 contents = open(logfile, "r")
 except IOError, e:
 print 'Error openning file '+ logfile +": "+e.strerror
 raise
 except:
 print 'Error openning file '+ logfile
 raise
 # go through each line of the logfile
 for line in contents:
 # split the string to isolate the IP address and date
 ip = line.split(" ", 1)[0]
 dateRecord=line.split(" ",5)[4][1:]
 date = dateRecord.split(":",1)[0]
 day=date.split("/",1)[0]
 month=date.split("/",2)[1]
 year =date.split("/",3)[2]
 hour = dateRecord.split(":",2)[1]
 minute = dateRecord.split(":",3)[2]
 seconds = dateRecord.split(":",4)[3]
 eventdate=datetime.datetime(int(year),int(months[month]),int(day),int(hour),int(minute),int(seconds))
 #Check if ip is right
 #print "Analizing IP: '" + ip + "'"
 if isValid(ip):
 if eventdate > today - refDay:
 intervalDelta=datetime.timedelta(seconds=int(sec))
 count=False
 if (ip in ipHitListing): 
 for ipData in ipHitListing[ip].items():
 if eventdate < ipData[0] + intervalDelta:
 ipHitListing[ip]= {ipData[0]:int(ipData[1])+1}
 count=True
 break
 if count == False:
 ipHitListing[ip]= {eventdate:1}
 else:
 ipHitListing[ip]= {eventdate:1}
 return ipHitListing
def main():
 parser = optparse.OptionParser("usage%prog " + "[-f <file>] -n Number of events -s seconds to find events -d number of days to analice -H number of hours to analice -m number of minutes to analice")
 parser.add_option('-f', dest = 'file', type = 'string', help = 'Please, specify the Apache access file', default="/var/log/apache2/access.log")
 parser.add_option('-n', dest = 'number', type = 'string', help = 'Please, specify the number of connections to detect. By default 10', default="10")
 parser.add_option('-d', dest = 'deltaday', type = 'string', help = 'Please, specify the number of days to check. By default 10', default="10")
 parser.add_option('-H', dest = 'deltahour', type = 'string', help = 'Please, specify the number of hours to check. By default 10', default="10")
 parser.add_option('-m', dest = 'deltaminute', type = 'string', help = 'Please, specify the number of minutes to check. By default 10', default="10")
 parser.add_option('-s', dest = 'seconds', type = 'string', help = 'Please, specify the seconds. By default: 1"', default="1")
 (options, args) = parser.parse_args()
 HitsDictionary = readHistoryIP(options.file,options.seconds, options.deltaday, options.deltahour, options.deltaminute)
 for ip in HitsDictionary.keys():
 if HitsDictionary[ip].values()[0] > int(options.number):
 print "Attention---> IP: " + ip + ", " + str(HitsDictionary[ip].values()[0]) + " access in " + options.seconds + " seconds"
if __name__ == "__main__":
 main()

Referencias:

  1. Python para todos, de Raúl González Duque
  2. Python cookbook, de Alex Martelli, Anna Martelli y David Ascher, O’Reilly