├── README.md ├── bgp-db.py └── bgpmon.py /README.md: -------------------------------------------------------------------------------- 1 | BGPmon 2 | ====== 3 | 4 | BGPmon 5 | 6 | BGP Hikack Monitoring 7 | 8 | optional arguments: 9 | -h, --help show this help message and exit 10 | -b, --baseline Baseline records 11 | -c, --check Check for any discrepancies in database 12 | -e EMAIL, --email EMAIL 13 | Add Email to database 14 | -ip IP, --ip IP Add IP to database 15 | 16 | Saif El-Sherei 17 | 18 | 19 | 20 | I. Introduction: 21 | 22 | BGPmon monitors your bgp route for hijacking and sends email alerts whenever discrepencies is found between the baseline 23 | and the latest update records, it utilizes "Team Cymru" IP to ASN tool using bulk queries. BGP hijack monitor grabs the 24 | originating AS for a list of IPs saved in the database. and if the "-b" switch is supplied will insert the result in the 25 | baseline table. if no switched are supplied the results will be saved in the latest Update tables. 26 | 27 | The tool utilitzed 'Team Cymru' IP to ASN tool. i would like to extend my special appreciation and thanks to this group 28 | for providing such a service. 29 | 30 | II. Installation: 31 | 32 | create database 'bgpmon' with user 'bgpmon' and password make sure to update both the bgp-db.py and bgpmon.py with 33 | the db name, dbhost, db user, db password. 34 | 35 | update db details in 'bgpmon.py' line 26 36 | update db details in 'bgp-db.py' line 5 37 | 38 | run the bgp-db.py script to create the required tables. 39 | 40 | add IP with '-ip' switch to be monitored 41 | 42 | add email with '-e' swtich to be alerted 43 | 44 | 45 | II. Usage: 46 | 47 | since this tool is made to be running in the cli please note that all std_out is saved in the log file '/var/log/bgp_mon.log' 48 | if you want to cancel this behaviour just comment out line 21 in 'bgpmon.py' script. you will see the output on your terminal 49 | 50 | 51 | 52 | ./bgpmon.py -e [EMAIL] 53 | 54 | Add Email to the emails table to be alerted. 55 | 56 | ./bgpmon.py -ip [IP] 57 | 58 | Add IP to the ips table to be monitored. 59 | 60 | ./bgpmon.py -b 61 | 62 | grabs the origin AS for the IPs in the Database and save the results in the base_line tables 63 | 64 | ./bgpmon.py 65 | 66 | grabs the origin AS for the IPs in the Database and save the results in the latest_update table. and 67 | checks for differences between latest_update and base_line. 68 | 69 | ./bgpmon.py -c 70 | 71 | Manual check the records with MAX time stamp in 'latest_update' table with records in the 'base_line' table 72 | for the each ip for differences if any difference is found send email to the saved emails. 73 | -------------------------------------------------------------------------------- /bgp-db.py: -------------------------------------------------------------------------------- 1 | import MySQLdb as mysql 2 | import sys 3 | 4 | try: 5 | conn = mysql.connect('localhost','bgpmon','bgpmon','bgpmon') 6 | with conn: 7 | 8 | print "[*] Creating table watched" 9 | cur = conn.cursor() 10 | cur.execute("drop table if exists watched") 11 | cur.execute("create table watched (id int primary key auto_increment, network varchar(100), time_stamp timestamp)") 12 | 13 | print "[*] Creating Table base_line" 14 | cur.execute("drop table if exists base_line") 15 | cur.execute("create table base_line (id int primary key auto_increment, network int, Origin_AS int, IP varchar(100), BGP_prefix varchar(100), CC varchar(5), Registry varchar(25), Allocated datetime, AS_Name varchar(300), time_stamp timestamp, constraint fk_IP foreign key (network) references watched(id) on update cascade on delete cascade)") 16 | 17 | print "[*] Creating Table latest_update" 18 | cur.execute("drop table if exists latest_update") 19 | cur.execute("create table latest_update (id int primary key auto_increment, network int, Origin_AS int, IP varchar(100), BGP_prefix varchar(100), CC varchar(5), Registry varchar(25), Allocated datetime, AS_Name varchar(300), time_stamp timestamp, constraint fk_IP_latest foreign key (network) references watched(id) on update cascade on delete cascade)") 20 | 21 | print "[*] Creating Table emails" 22 | cur.execute("drop table if exists emails") 23 | cur.execute("create table emails(id int primary key auto_increment, email varchar(100)") 24 | 25 | print "[*] Creating Table Validate" 26 | cur.execute("drop table if exists validate") 27 | cur.execute("create table validate(id int primary key auto_increment, network varchar(50), diff_type varchar(50), diff_rec varchar(1024), time_stamp timestamp)") 28 | 29 | 30 | print "[*] Creating Table Alert_history" 31 | cur.execute("drop table if exists alert_hitory") 32 | cur.execute("create table alert_history (id int primary key auto_increment, network varchar(50), diff_type varchar(50), diff_rec varchar(1024), time_stamp timestamp)") 33 | 34 | 35 | 36 | except mysql.Error, e: 37 | 38 | print "[*] Error %d, %s" % (e.args[0], e.args[1]) 39 | sys.exit(1) 40 | 41 | 42 | -------------------------------------------------------------------------------- /bgpmon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import socket 4 | import argparse 5 | import MySQLdb as mysql 6 | import smtplib 7 | import email.utils 8 | from email.mime.text import MIMEText 9 | import sys 10 | import cStringIO 11 | import time 12 | 13 | 14 | 15 | class start(): 16 | 17 | def __init__(self): 18 | self.db_host = 'localhost' 19 | self.db_user = 'bgpmon' 20 | self.db_pass = 'bgpmon' 21 | self.db = 'bgpmon' 22 | # sys.stdout = open('/var/log/bgp-mon.log','a') 23 | pass 24 | 25 | def sql_conn(self): 26 | try: 27 | self.conn = mysql.connect(self.db_host,self.db_user,self.db_pass,self.db) 28 | 29 | except mysql.Error, self.e: 30 | 31 | print "Error %d:%s" % (self.e.args[0], self.e.args[1]) 32 | sys.exit(1) 33 | 34 | 35 | def sql_populate(self,b=None): 36 | 37 | if b: 38 | self.table = 'base_line' 39 | self.cur = self.conn.cursor() 40 | 41 | self.cur.execute("select * from %s where network = (select id from watched where network = %%s)" % self.table, (self.info)) 42 | if self.cur.fetchone() == None: 43 | 44 | self.cur.execute("insert into %s (network) values ((select id from watched where network = %%s))" % self.table, (self.info)) 45 | 46 | self.cur.execute("update %s set Origin_AS = %%s, IP = %%s, BGP_prefix = %%s, CC = %%s, Registry = %%s, Allocated = %%s, AS_Name = %%s, time_stamp=NOW() where network = (select id from watched where network = %%s)" % self.table, (self.origin_AS,self.IP,self.BGP_prefix,self.CC,self.Registry,self.Allocated,self.AS_name,self.info)) 47 | 48 | else: 49 | self.cur.execute("update %s set Origin_AS = %%s, IP = %%s, BGP_prefix = %%s, CC = %%s, Registry = %%s, Allocated = %%s, AS_Name = %%s, time_stamp=NOW() where network = (select id from watched where network = %%s)" % self.table, (self.origin_AS,self.IP,self.BGP_prefix,self.CC,self.Registry,self.Allocated,self.AS_name,self.info)) 50 | 51 | self.conn.commit() 52 | 53 | else: 54 | self.table = 'latest_update' 55 | self.cur = self.conn.cursor() 56 | 57 | print '[*] Adding Entry to latest_update' 58 | self.cur.execute("insert into %s (network,Origin_AS,IP,BGP_prefix,CC,Registry,Allocated,AS_Name) values ((select id from watched where network like %%s),%%s,%%s,%%s,%%s,%%s,%%s,%%s)" % self.table, (self.info,self.origin_AS,self.IP,self.BGP_prefix,self.CC,self.Registry,self.Allocated,self.AS_name)) 59 | 60 | self.conn.commit() 61 | 62 | def myrecv(self,timeout=2): 63 | # make socket non-blocking i.e. if no data is recieved break connection 64 | self.connection.setblocking(0) 65 | # total data in an array 66 | self.total_reply=[] 67 | self.rdata= '' 68 | #begining time 69 | self.begin=time.time() 70 | while True: 71 | # if we get some data, then break after time out 72 | if self.total_reply and time.time()-self.begin>timeout: 73 | break 74 | # if we didnt get any data break after timeout*2 75 | elif time.time()-self.begin>timeout*2: 76 | break 77 | # recive data 78 | try: 79 | self.rdata = self.connection.recv(2048) 80 | if self.rdata: 81 | self.total_reply.append(self.rdata) 82 | #reset the begining time 83 | self.begin = time.time() 84 | else: 85 | # sleep for sometime to indicate gap 86 | time.sleep(0.1) 87 | except: 88 | pass 89 | #join all parts to make final reply 90 | return ''.join(self.total_reply) 91 | 92 | 93 | def magic(self, b=None): 94 | 95 | self.sql_conn() 96 | self.cur = self.conn.cursor() 97 | self.cur.execute('select network from watched') 98 | self.buffer = 'begin\r\nverbose\r\nend' 99 | print "[*] Querying Team Cymru Whois Server for Orignating AS" 100 | for self.query in self.cur: 101 | self.index = self.buffer.index('end') 102 | self.buffer = self.buffer[0:self.index] + self.query[0] + ' ' + self.query[0] + '\r\n' + self.buffer[self.index:] 103 | self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 104 | # print self.buffer 105 | try: 106 | self.connection.connect(('whois.cymru.com',43)) 107 | except: 108 | print "[*] Communication Error" 109 | sys.exit(1) 110 | self.connection.send(self.buffer) 111 | self.reply = self.myrecv() 112 | self.reply = cStringIO.StringIO(self.reply) 113 | self.reply = self.reply.readlines() 114 | for self.item in self.reply[1:]: 115 | self.data = self.item.split('|') 116 | self.origin_AS = self.data[0].strip() 117 | self.IP = self.data[1].strip() 118 | self.BGP_prefix = self.data[2].strip() 119 | self.CC = self.data[3].strip() 120 | self.Registry = self.data[4].strip() 121 | self.Allocated = self.data[5].strip() 122 | self.info = self.data[6].strip() 123 | self.AS_name = self.data[7].strip() 124 | print """ 125 | Query: %s 126 | Origin AS: %s 127 | IP: %s 128 | BGP_prefix: %s 129 | CC: %s 130 | Registr: %s 131 | Allocated: %s 132 | AS_name: %s""" % (self.info, self.origin_AS,self.IP,self.BGP_prefix,self.CC,self.Registry,self.Allocated,self.AS_name) 133 | if b: 134 | self.sql_populate(b) 135 | else: 136 | self.sql_populate() 137 | self.conn.close() 138 | 139 | def add_email(self,email): 140 | 141 | self.email = email 142 | self.sql_conn() 143 | self.cur = self.conn.cursor() 144 | self.cur.execute("insert into emails (email) values (%s)", self.email) 145 | self.conn.commit() 146 | print "[*] Adding Email %s to table\r\n" % self.email 147 | self.cur.close() 148 | self.conn.close() 149 | 150 | def add_ip(self,network): 151 | 152 | self.network = network 153 | self.sql_conn() 154 | self.cur = self.conn.cursor() 155 | self.cur.execute("insert into watched (network) values (%s)",self.network) 156 | self.conn.commit() 157 | print "[*] Adding IP %s to table\r\n" % self.network 158 | self.cur.close() 159 | self.conn.close() 160 | 161 | def reccmp(self,rec1,rec2): 162 | 163 | self.rec1 = rec1 164 | self.rec2 = rec2 165 | if self.rec1 == self.rec2: 166 | return True 167 | else: 168 | return False 169 | 170 | def send_email(self,msg): 171 | 172 | self.msg = MIMEText(msg) 173 | self.sql_conn() 174 | self.cur = self.conn.cursor() 175 | self.to = [] 176 | self.cur.execute("select email from emails") 177 | self.emails = self.cur.fetchall() 178 | for self.mail in self.emails: 179 | self.to.append(self.mail[0]) 180 | self.from_email = 'bgp-mon@alert-moi.com' 181 | self.msg['subject'] = 'ISC BGP hijack monitor Alert' 182 | self.server = smtplib.SMTP('localhost',25) 183 | #self.server.set_debuglevel(True) 184 | self.server.starttls() 185 | self.server.sendmail(self.from_email,self.to,self.msg.as_string()) 186 | self.server.quit() 187 | 188 | 189 | def checking(self): 190 | self.msg = "" 191 | self.sql_conn() 192 | self.cur = self.conn.cursor() 193 | self.cur2 = self.conn.cursor() 194 | self.cur3 = self.conn.cursor() 195 | self.cur.execute('select network,Origin_AS,IP,BGP_prefix,CC,Registry,Allocated,AS_Name from base_line') 196 | for self.row in self.cur: 197 | self.network_BL,self.Origin_AS_BL, self.IP_BL, self.BGP_prefix_BL,self.CC_BL,self.Registry_BL,self.Allocated_BL,self.AS_Name_BL = self.row 198 | self.cur2.execute('select MAX(time_stamp) from latest_update where network = %s', (self.network_BL)) 199 | self.ts = self.cur2.fetchone() 200 | self.cur2.execute('select network,Origin_AS,IP,BGP_prefix,CC,Registry,Allocated,AS_Name from latest_update where network = %s and time_stamp = %s', (self.network_BL, self.ts[0])) 201 | self.row_latest = self.cur2.fetchone() 202 | self.network_LU,self.Origin_AS_LU, self.IP_LU, self.BGP_prefix_LU,self.CC_LU,self.Registry_LU,self.Allocated_LU,self.AS_Name_LU = self.row_latest 203 | self.diff = False 204 | if not self.reccmp(self.Origin_AS_BL,self.Origin_AS_LU): 205 | self.diff = True 206 | self.dtype = 'Origin_AS' 207 | self.diff_rec = self.Origin_AS_LU 208 | elif not self.reccmp(self.IP_BL, self.IP_LU): 209 | self.diff = True 210 | self.dtype = 'IP' 211 | self.diff_rec = self.IP.LU 212 | elif not self.reccmp(self.BGP_prefix_BL, self.BGP_prefix_LU): 213 | self.diff = True 214 | self.dtype = 'BGP_prefix' 215 | self.diff_rec = self.BGP_prefix_LU 216 | elif not self.reccmp(self.CC_BL, self.CC_LU): 217 | self.diff = True 218 | self.dtype = 'CC' 219 | self.diff_rec = self.CC_LU 220 | elif not self.reccmp(self.Registry_BL, self.Registry_LU): 221 | self.diff = True 222 | self.dtype = 'Registry' 223 | self.diff_rec = self.Registry_LU 224 | elif not self.reccmp(self.Allocated_BL, self.Allocated_LU): 225 | self.diff = True 226 | self.dtype = 'Allocated' 227 | self.diff_rec = self.Allocated_LU 228 | elif not self.reccmp(self.AS_Name_BL, self.AS_Name_LU): 229 | self.diff = True 230 | self.dtype = 'AS_Name' 231 | self.diff_rec = self.AS_Name_LU 232 | else: 233 | print "--------Network:%s----------\r\n" % self.network_LU 234 | print "[*} Orginating ASN is in baseline: %s" % self.Origin_AS_LU 235 | print "[*] Requested IP address is in baseline: %s" % self.IP_LU 236 | print "[*] Announced BGP network Prefix is in baseline: %s" % self.BGP_prefix_LU 237 | print "[*] Country Code is in baseline: %s" % self.CC_LU 238 | print "[*] Network Registry is in baseline: %s" % self.Registry_LU 239 | print "[*] Date of Allocation is in baseline: %s" % self.Allocated_LU 240 | print "[*] AS Name is in baseline: %s\r\n\r\n" % self.AS_Name_LU 241 | 242 | if self.diff: 243 | 244 | self.msg = """ 245 | -------- Different Entry: %s ------ Network: %s --------- 246 | [*] Error Matching Record to Baseline 247 | [*] Latest Record: Origin ASN %s, Baseline: Origin ASN %s 248 | [*] Latest Record: IP %s, Baseline: IP %s 249 | [*] Latest Record: BGP Prefix %s, Baseline: BGP Prefix %s 250 | [*] Latest Record: CounrtyCode %s, Baseline: CountryCode %s 251 | [*] Latest Record: Registry %s, Baseline: Registry %s 252 | [*] Latest Record: Allocation %s, Baseline: Allocation %s 253 | [*] Latest Record: AS Name %s, Baseline: AS Name %s\r\n\r\n""" % (self.rec2, self.network_BL, self.Origin_AS_LU, self.Origin_AS_BL, self.IP_LU, self.IP_LU, self.BGP_prefix_LU, self.BGP_prefix_BL, self.CC_LU, self.CC_BL, self.Registry_LU, self.Registry_BL, self.Allocated_LU, self.Allocated_LU, self.AS_Name_LU, self.AS_Name_BL) 254 | # self.send_email(self.msg) 255 | self.done = False 256 | self.cur3.execute("select %s,network from latest_update where network=%%s and time_stamp != %%s" % self.dtype, (self.network_LU,self.ts[0])) 257 | for self.r in self.cur3: 258 | self.check, self.hack = self.r 259 | if self.reccmp(self.check,self.diff_rec): 260 | self.done = True 261 | if self.done: 262 | self.cur3.execute("select network,diff_type,diff_rec from validate where network=(select network from watched where id=%s) and diff_type=%s and diff_rec=%s", (self.network_LU, self.dtype, self.diff_rec)) 263 | if self.cur3.fetchone() == None: 264 | self.cur3.execute("insert into validate (network,diff_type,diff_rec) values ((select network from watched where id =%s),%s,%s)", (self.network_LU, self.dtype, self.diff_rec)) 265 | self.conn.commit() 266 | print "[*] Differene already alerted Adding New %s Record to validation table: %s" % (self.dtype,self.diff_rec) 267 | else: 268 | print "[*] Item Already in Validation Table" 269 | else: 270 | print "Aleeeeeeeeeeeeeeerrrrrrrrrrrt!!!!" 271 | print self.msg 272 | self.send_email(self.msg) 273 | self.conn.close() 274 | self.cur.close() 275 | self.cur2.close() 276 | self.cur3.close() 277 | 278 | def validate(self,d=None): 279 | 280 | self.answer = False 281 | self.sql_conn() 282 | self.cur = self.conn.cursor() 283 | 284 | if d: 285 | self.d = d 286 | self.cur.execute("select id,network, diff_type, diff_rec from validate where network = (select id from watched where netowrk = %s)", self.d) 287 | print "[*] Gathering Entries that need validation from database for domain: %s" % self.d 288 | else: 289 | self.cur.execute("select id,network,diff_type,diff_rec from validate") 290 | print "[*] Gathering Entries that need validation for all QUERIES" 291 | 292 | if not self.cur.fetchone(): 293 | print "[*] No Entries Found for specified query" 294 | else: 295 | for self.r in self.cur: 296 | 297 | self.id_v,self.network_v, self.dtype_v, self.diff_rec_v = self.r 298 | self.cur.execute("select network from watched where id = %s", self.network_v) 299 | self.d = self.cur.fetchone()[0] 300 | print "[*] Entry for network %s Different Record %s for Type %s" % (self.d, self.diff_rec_v, self.dtype_v) 301 | print "[*] please enter choice [Y]Yes, [N]No" 302 | self.question = raw_input("Validate Entry? ") 303 | while not self.answer: 304 | if self.question == 'Y': 305 | 306 | self.cur.execute("update baseline set %s=case when %s is null then %%s when %s like %%s then %%s else concat_ws(',',%s,%%s) end,time_stamp=now() where netowrk=%s" % (self.dtype_v, self.dtype_v, self.dtype_v, self.dtype_v), (self.diff_rec_v, self.diff_rec_v, self.diff_rec_v, self.diff_rec_v, self.network_v)) 307 | 308 | self.cur.execute("delete from validate where id = %s", self.id_v) 309 | print "[*] Malicious Entry Validated" 310 | print "[*] New Entry Added to Baseline." 311 | self.answer = True 312 | 313 | elif self.question == 'N': 314 | self.cur.execute("insert into alert_history(network,diff_type,diff_rec) values (%s,%s,%s)", (self.d, self.dtype_v, self.diff_rec_v)) 315 | self.cur.execute("delete from validate where id = %s", self.id_v) 316 | self.answer = True 317 | self.msg_v = """ 318 | Maliciouss entry found after validation for: 319 | [*] Domain: %s 320 | [*] Query Type: %s 321 | [*] Malicious Entry: %s""" % (self.d, self.dtype_v, self.diff_rec_v) 322 | print self.msg_v 323 | self.send_email(self.msg) 324 | else: 325 | print "[*] Invalid Choicei,Try again" 326 | 327 | self.conn.commit() 328 | self.cur.close() 329 | 330 | parser = argparse.ArgumentParser(description = 'BGP Hikack Monitoring', epilog = 'Saif El-Sherei') 331 | parser.add_argument('-b','--baseline',help = 'Baseline records',action = 'store_true') 332 | parser.add_argument('-c','--check',help = 'Check for any discrepancies in database',action='store_true') 333 | parser.add_argument('-e','--email',help = 'Add Email to database') 334 | parser.add_argument('-ip','--ip',help = 'Add IP to database') 335 | p = parser.parse_args() 336 | c = start() 337 | 338 | if p.baseline and not p.check and not p.email and not p.ip: 339 | 340 | c.magic(p.baseline) 341 | 342 | if not p.baseline and not p.check and not p.email and not p.ip: 343 | 344 | c.magic() 345 | c.checking() 346 | 347 | if p.check and not p.baseline and not p.email and not p.ip: 348 | 349 | c.checking() 350 | 351 | if p.email and not p.baseline and not p.check and not p.ip: 352 | 353 | c.add_email(p.email) 354 | 355 | if p.ip and not p.baseline and not p.check and not p.email: 356 | 357 | c.add_ip(p.ip) 358 | c.magic(p.baseline) 359 | 360 | --------------------------------------------------------------------------------