SnortWarn

This python script uses snort's database event logs and the whois database to send scheduled e-mails to abuse ISP-s about incidents.

#!/usr/bin/python
import sys
import os
import re
import pprint
from pyPgSQL import PgSQL
from sets import Set
import smtplib
from email.MIMEText import MIMEText
from email.MIMEMultipart import MIMEMultipart
 
_DB=None
_DBname="snortdb"
_DBuser="snortdb"
_DBpass="password"
_DBhost="localhost"
 
primaryrecipient="email@add.ress.hu"
treshold=5 # minimum no. of events to mail
 
mailhead="""
 
Hello!
 
This is an automatically generated message from the netpolice robot
at MTA SZTAKI Network Security Department. You can reach us at
netpolice@sztaki.hu so please DO NOT REPLY to this mail's sending
address as your reply will be silently discarded.
 
We are to inform you about several security violations originating
from your address space. Please note that our detection system uses
an IP packet pattern database so false positives can never be
completely eliminated. If you are sure that our warning was pointless
please discard this letter, we are sorry for your inconvenience.
 
Your e-mail address has been carefully harvested from the attached
whois record. If this message is not for you to concern please tell
the responsible person or group to set up an e-mail address beginning
with the word 'abuse' and ask your whois administrator to update
the record. Our system cannot distinguish between different abuse 
adresses established for different types of incidents.
 
----------------------------------------------------------------------
 
The following activities were detected (you can find more information
as the destination port address and other details on the URLs below):
 
%s
The activities above were detected from the following source
addresses:
 
%s
The detailed transcript of the events follows. Note that our system 
logs the same action from the same source or destination address 
once per 60 seconds so the actual event rate could be higher.
 
My time zone is CE(S)T (CET with or without daylight saving). See:
http://www.timeanddate.com/worldclock/city.html?n=50
 
----------------------------------------------------------------------
 
"""
 
print "snortwarn.py starting ...\n"
 
pp = pprint.PrettyPrinter(indent=4)
 
def int2dquad(int32):
    a=str((int32 & 0x0ff000000) >> 24)
    b=str((int32 & 0x000ff0000) >> 16)
    c=str((int32 & 0x00000ff00) >> 8)
    d=str((int32 & 0x0000000ff))
    return a+"."+b+"."+c+"."+d
 
def db_connect():
    global _DB
 
    try:
        _DB = PgSQL.connect(database=_DBname,user=_DBuser,password=_DBpass,host=_DBhost)
    except PgSQL.Error, msg:
        print "Connection to database '%s' failed" % dbname
        print msg,
        sys.exit()
    return _DB
 
def db_query(query):
    global _DB
 
    if _DB is None:
        _DB=db_connect()
 
    cur = _DB.cursor()
 
    try:
        cur.execute(query)
 
    except PgSQL.Error, msg:
        print "db_query failed\n%s" % msg,
        sys.exit()
 
    desc={};
    i=0
    for r in cur.description:
        desc[i]=r[0]
        i=i+1
 
    try:
        cur.arraysize=1000;
        res = cur.fetchall()
    except StandardError, msg:
        print "db_query: fetch of all instanaces failed\n%s" % msg,
        sys.exit()
 
    result={}
    i=0
    for r in res:
        u=0
        result[i]={}
        for d in desc.values():
            result[i][d] = r[u]
            u=u+1
        i=i+1
 
    cur.close()
 
    _DB.commit()
 
    return result
 
 
print "fetching alerts from database ..."
 
res=db_query("""
    select
      event.\"timestamp\",signature.sig_name,signature.sig_sid,iphdr.ip_src,iphdr.ip_dst 
    from
      event,signature,iphdr 
    where
      iphdr.ip_ttl>0 and
      iphdr.cid=event.cid and
      signature.sig_id=event.signature and
      event.\"timestamp\"> timestamp 'now' - interval '24 hours'
      and signature in (
        select
          s.sig_id
        from
          signature s,sig_class c
        where
          s.sig_class_id=c.sig_class_id and
          c.sig_class_name in (
            'web-application-attack','attempted-admin','misc-attack','denial-of-service','attempted-dos'
          )       
      )
    """)
#      order by timestamp 
 
print "got " + str(len(res)) + " events ..."
 
# group by ip
ips={}
for r in res.values():
    src=int2dquad(r['ip_src'])
    if not ips.has_key(src):
        ips[src]={'count':0, 'alerts': Set(), 'events':{}}    
    ips[src]['count']+=1
    ips[src]['events'][r['timestamp']]=r
    ips[src]['alerts']|=Set([str(r['sig_sid'])+"|"+r['sig_name']])
 
#pp.pprint(ips)
 
# fetch whois for each ip
for r in ips:
    if ips[r]['count']<3:
	# drop low noise ips
	print "dropping "+r+" (bellow alarm treshold)..."
	ips[r]=None
	continue
    print "collecting whois data for "+r+" ..."
    whois=''
    whoisp=os.popen("jwhois "+r)
    whois+=whoisp.read(1024000);
    whoisp.close();
#    whoisp=os.popen("jwhois +"+r)
#    whois+=whoisp.read(1024000);
#    whoisp.close();
 
    e=None
    mailregex="[0-9a-z_.-]*@[0-9a-z_.-]+[.][a-z]+"
    s=Set()
    s|=Set(re.compile('(?ims)([0-9a-z_.-]*abuse'+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)abuse.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)incident.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)spam.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)role.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)person.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)route.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)notify.*?('+mailregex+')').findall(whois))
    if len(s) == 0:
        s|=Set(re.compile('(?ims)('+mailregex+')').findall(whois))
    if len(s) > 0:
        e=list(s)
 
    # loopback or private ips: no notify
    if len(re.compile('(?ims)(127[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}|172[.]16[.][0-9]{1,3}[.][0-9]{1,3}|192[.]168[.][0-9]{1,3}[.][0-9]{1,3})').findall(r))>0:
	print "  loopback or private ip " + str(r);
#        e=[primaryrecipient]
        e=["mcree@sztaki.hu"]
 
    # do not notify ripe or arin technical addresses and our own address
    if len(re.compile('(?ims)(@ripe.net|@arin.net|@arpnic.net|@nic[.])').findall(str(e)))>0:
	print "  dropping whois technical mail address " + str(e);
#        e=[primaryrecipient]
        e=["mcree@sztaki.hu"]
 
    # local address
    if len(re.compile('(?ims)(@sztaki.hu)').findall(str(e)))>0:
	print "  dropping whois technical mail address " + str(e);
#        e=[primaryrecipient]
        e=["mcree@sztaki.hu", "btoth@sztaki.hu"]
 
    ips[r]['whois']=whois
 
    if e is not None:
        ips[r]['email']=e
    else:
        ips[r]['email']=None
        print "  WARNING: no mail address found!"
 
#pp.pprint(ips)
 
# group by emails
emails={}
for r in ips:
    if ips[r] is not None:
        e=ips[r]['email']
	if e is not None:
    	    if not emails.has_key(str(e)):
        	emails[str(e)]={'count':0, 'alerts':Set(), 'whois':ips[r]['whois'], 'ips':[], 'email':e}
    	    emails[str(e)]['ips']+=[r]
	    emails[str(e)]['count']+=ips[r]['count']
    	    emails[str(e)]['alerts']|=ips[r]['alerts']
 
#pp.pprint(emails)
#pp.pprint(ips)
 
for e in emails:
    if emails[e]['count']>=treshold:
        print "sending mail to "+e
        attsum=""
        for a in emails[e]['alerts']:
            (sid,desc)=a.split('|')
            attsum+=desc+"\n"
            attsum+="    -> http://www.snort.org/snort-db/sid.html?sid="+sid+"\n"
            ipseach=""
            ipsuml=[]
            for i in emails[e]['ips']:
                ipseach+=i+" ("+str(ips[i]['count'])+" event(s))\n"
                for a in ips[i]['alerts']:
                    (sid,desc)=a.split('|')
                    ipseach+="    "+desc+"\n"
                for v in ips[i]['events'].values():
                    ipsuml+=[str(v['timestamp'])+'  '+int2dquad(v['ip_src'])+" -> "+int2dquad(v['ip_dst'])+'  '+v['sig_name']+"\n"]
                ipseach+=""
            ipsuml.sort()
            if(len(ipsuml)>100):
                ipsuml=ipsuml[0:99]
            ipsum=""
            ipsum=ipsum.join(ipsuml)
            ipsum="Transcript follows (limited to 100 records):\n\n"+ipsum+"\n----------------------------------------------------------------------\n\n"
 
        #print mailhead % (attsum, ipseach)
        #print ipsum
 
        commaspace=', '
        msg=MIMEMultipart()
        msg['Subject']="Security Notification"
        msg['From']="devnull@sztaki.hu"
        msg['To']=commaspace.join([primaryrecipient] + emails[e]['email'])
        msg.attach(MIMEText((mailhead % (attsum, ipseach)) + ipsum))
#        msg.attach(MIMEText(ipsum))
        msg.attach(MIMEText("Whois query result follows:\n\n"+emails[e]['whois']))
        msg.preamble = 'You should view this message MIME-aware mail reader.\n'
        msg.epilogue = ''
 
        s = smtplib.SMTP()
        s.connect()
	for recip in ([primaryrecipient] + emails[e]['email']):
#	    print "sending mail to " + str(recip)
	    s.sendmail("devnull@sztaki.hu", recip, msg.as_string())
        s.close()
 
 
        #print msg.as_string()