Package routerdefense :: Module common
[hide private]
[frames] | no frames]

Source Code for Module routerdefense.common

  1  # -*- coding: iso-8859-1 -*- 
  2   
  3  __docformat__ = 'restructuredtext' 
  4  __version__ = '$Id$' 
  5   
  6  import __builtin__ 
  7  import re 
  8  import random 
  9  import string 
 10  import os 
 11  import sys 
 12   
 13  from xml.dom.minidom import parse 
 14   
 15  __builtin__.iosVersion = None 
 16   
17 -def read_cfg(File):
18 """Read the show run file.""" 19 lines = [] 20 try: 21 for line in open(File, 'r'): 22 lines.append(line.rstrip()) 23 except IOError: 24 print "The IOS configuration file does not exists. Please fix run_routerdefense." 25 exit(1) 26 return lines
27
28 -def check_cfg(lines):
29 """Validate that the configuration file is from a Cisco IOS.""" 30 validatedConfig = 1 31 for line in lines: 32 if line.startswith('nameif') == True : # PIX/ASA 33 validatedConfig = 0 34 break 35 if line.startswith('feature') == True : # NX-OS 36 validatedConfig = 0 37 break 38 if validatedConfig == 0: 39 print "This is not an IOS configuration file." 40 exit(1) 41 return
42
43 -class Item:
44 """Item class definition.""" 45 name = None 46 fixImpact = None 47 definition = None 48 threatInfo = None 49 howtofix = None 50 upgrade = None 51 cvssMetric = None 52
53 - def __init__(self):
54 pass
55
56 -class Xml2Data:
57 """Convert XML to DATA.""" 58 __currentNode__ = None 59 __itemsList__ = None 60
61 - def __init__(self):
62 self.read_xml()
63 - def get_root(self):
64 if self.__currentNode__ is None: 65 self.__currentNode__ = self.xmldoc.documentElement 66 return self.__currentNode__
67
68 - def get_items(self):
69 if self.__itemsList__ is not None: 70 return 71 self.__itemsList__ = [] 72 for lines in self.get_root().getElementsByTagName("item"): 73 if lines.nodeType == lines.ELEMENT_NODE: 74 obj = Item() 75 try: 76 obj.name = self.get_text( 77 lines.getElementsByTagName("name")[0]) 78 obj.fixImpact = self.get_text( 79 lines.getElementsByTagName("fixImpact")[0]) 80 obj.definition = self.get_text( 81 lines.getElementsByTagName("definition")[0]) 82 obj.threatInfo = self.get_text( 83 lines.getElementsByTagName("threatInfo")[0]) 84 obj.howtofix = self.get_text( 85 lines.getElementsByTagName("howtofix")[0]) 86 obj.upgrade = self.get_text( 87 lines.getElementsByTagName("upgrade")[0]) 88 obj.cvssMetric = self.get_text( 89 lines.getElementsByTagName("CVSSmetric")[0]) 90 except xml.dom.DOMException: 91 pass 92 self.__itemsList__.append(obj) 93 return self.__itemsList__
94
95 - def get_text(self, node):
96 return node.childNodes[0].nodeValue
97 - def read_xml(self):
98 fsock = open('ciscoObjects.xml') 99 self.xmldoc = parse(fsock) 100 fsock.close()
101
102 -def search_xml(name):
103 items = [] 104 xmlFile = Xml2Data() 105 nbObjects = len(xmlFile.get_items()) 106 for x in range(0, nbObjects): 107 if name in xmlFile.__itemsList__[x].name: 108 items.append(xmlFile.__itemsList__[x].fixImpact) 109 items.append(xmlFile.__itemsList__[x].definition) 110 items.append(xmlFile.__itemsList__[x].threatInfo) 111 items.append(xmlFile.__itemsList__[x].howtofix) 112 items.append(xmlFile.__itemsList__[x].upgrade) 113 items.append(xmlFile.__itemsList__[x].cvssMetric) 114 return items 115 return 0
116
117 -def stripping(line):
118 """Left and right strip a specific line.""" 119 strippedLine = line.lstrip().rstrip() 120 return strippedLine
121
122 -def search_string(iosConfig, search_string):
123 """Search a string into a configuration block.""" 124 stringLookup = None 125 if sys.version_info < (2, 5): 126 for line in iosConfig: 127 if line.lower().rfind(search_string) != -1: 128 stringLookup = line 129 return stringLookup 130 else: 131 stringLookup = next( 132 (line for line in iosConfig if 133 line.lower().rfind(search_string) != -1), None) 134 return stringLookup
135
136 -def search_multi_string(iosConfig, search_string):
137 """Search multiple occurence of a string 138 into a configuration block. 139 140 """ 141 stringLookup = None 142 stringTable = [] 143 for line in iosConfig: 144 if line.lower().rfind(search_string) != -1: 145 stringTable.append(line) 146 return stringTable
147
148 -def search_re_string(iosConfig, search_string):
149 """Search a regex matching string into a configuration block.""" 150 stringLookup = None 151 if sys.version_info < (2, 5): 152 for line in iosConfig: 153 if re.search(search_string, line) is not None: 154 stringLookup = line 155 return stringLookup 156 else: 157 stringLookup = next( 158 (line for line in iosConfig if 159 re.search(search_string, line) is not None), None) 160 return stringLookup
161
162 -def search_re_multi_string(iosConfig, search_string):
163 """Search multiple occurence of a regex string 164 into a configuration block. 165 166 """ 167 stringLookup = None 168 stringTable = [] 169 for line in iosConfig: 170 stringLookup = re.search(search_string, line) 171 if stringLookup is not None: 172 stringTable.append(stringLookup.string) 173 return stringTable
174
175 -def search_string_count(iosConfig, search_string):
176 """Count occurence of a string.""" 177 stringCount = 0 178 for line in iosConfig: 179 if line.rfind(search_string) != -1: 180 stringCount = stringCount + 1 181 return stringCount
182
183 -def search_re_string_count(iosConfig, search_string):
184 """Count occurence of a regex string.""" 185 stringCount = 0 186 for line in iosConfig: 187 if re.search(search_string, line) is not None: 188 stringCount = stringCount + 1 189 return stringCount
190
191 -def parse_console(lines):
192 """Console port section.""" 193 consoleTable = [] 194 for i,v in enumerate(lines): 195 if v.rfind('line con 0') != -1: 196 lineConLocation = i 197 break 198 for i in range(lineConLocation + 1, len(lines)): 199 if (not lines[i].startswith("!") and 200 not lines[i].startswith("line")): 201 consoleTable.append(stripping(lines[i])) 202 else: 203 break 204 return consoleTable
205
206 -def parse_aux(lines):
207 """Aux port section.""" 208 auxTable = [] 209 lineAuxLocation = 0 210 for i,v in enumerate(lines): 211 if v.rfind('line aux 0') != -1: 212 lineAuxLocation = i 213 break 214 if lineAuxLocation != 0: 215 for i in range(lineAuxLocation + 1, len(lines)): 216 if (not lines[i].startswith("!") and 217 not lines[i].startswith("line")): 218 auxTable.append(stripping(lines[i])) 219 else: 220 break 221 222 return auxTable
223
224 -def parse_vty(lines):
225 """VTY section.""" 226 vtyTable = [] 227 lineVtyLocation = [] 228 for i,v in enumerate(lines): 229 if v.rfind('line vty') != -1: 230 lineVtyLocation.append(i) 231 vtyTable.append([stripping(lines[i])]) 232 for j in range( 0, len(lineVtyLocation) ): 233 for k in range(lineVtyLocation[j] + 1, len(lines)): 234 if (lines[k].startswith(" ") or 235 not lines[k].startswith("!") and 236 not lines[k].startswith("line")): 237 vtyTable[j].append(stripping(lines[k])) 238 else: 239 break 240 return vtyTable
241
242 -def parse_extd_acl(aclname):
243 """Parse extended ACL.""" 244 table = [] 245 location = [] 246 for i,v in enumerate(__builtin__.wholeconfig): 247 if v.rfind(aclname) != -1: 248 location.append(i) 249 table.append([stripping(__builtin__.wholeconfig[i])]) 250 for j in range( 0, len(location) ): 251 for k in range(location[j] + 1, len(__builtin__.wholeconfig)): 252 if not __builtin__.wholeconfig[k].startswith("!"): 253 table[j].append(stripping(__builtin__.wholeconfig[k])) 254 else: 255 break 256 return table
257 258
259 -def parse_motd(lines):
260 """Parse the MOTD banner.""" 261 bannerTable = [] 262 bannerStartLocation = 0 263 for i,v in enumerate(lines): 264 if v.rfind('banner motd') != -1: 265 bannerStartLocation = i 266 break 267 if bannerStartLocation == 0: 268 return bannerTable 269 else: 270 for i in range(bannerStartLocation + 1, len(lines)): 271 if not lines[i].startswith("!"): 272 bannerTable.append(stripping(lines[i])) 273 else: 274 break 275 276 return bannerTable
277
278 -def parse_exec_banner(lines):
279 """Parse the EXEC banner.""" 280 bannerTable = [] 281 bannerStartLocation = 0 282 for i,v in enumerate(lines): 283 if v.rfind('banner exec') != -1: 284 bannerStartLocation = i 285 break 286 if bannerStartLocation == 0: 287 return bannerTable 288 else: 289 for i in range(bannerStartLocation + 1, len(lines)): 290 if not lines[i].startswith("!"): 291 bannerTable.append(stripping(lines[i])) 292 else: 293 break 294 295 return bannerTable
296
297 -def parse_login_banner(lines):
298 """Parse the LOGIN banner.""" 299 bannerTable = [] 300 bannerStartLocation = 0 301 for i,v in enumerate(lines): 302 if v.rfind('banner login') != -1: 303 bannerStartLocation = i 304 break 305 if bannerStartLocation == 0: 306 return bannerTable 307 else: 308 for i in range(bannerStartLocation + 1, len(lines)): 309 if not lines[i].startswith("!"): 310 bannerTable.append(stripping(lines[i])) 311 else: 312 break 313 314 return bannerTable
315
316 -def stdout_content(definition, threatInfo, howtofix, impact, cvss):
317 """Format the reporting content for stdout.""" 318 str = '' 319 str = """ 320 => What: """ + definition + """ 321 => Threat: """ + threatInfo + """ 322 => Patch impact: """ + impact + """ 323 => Score: """ + cvss + """/10 324 => How to fix: \n """ + howtofix 325 326 return str
327
328 -def stdout_banner(categoryname):
329 """Print the category banner section.""" 330 return '=[ ' + categoryname
331
332 -def stdout_category_banner(categoryname):
333 """Print the category sections definitions.""" 334 catname = '' 335 if categoryname == 'ManagementPlane': 336 catname = 'Management plane' 337 catdef = """ 338 The management plane consists of functions \ 339 that achieve the management goals of the network. \ 340 This includes interactive management sessions using SSH, \ 341 as well as statistics-gathering with SNMP or NetFlow. \ 342 When you consider the security of a network device, \ 343 it is critical that the management plane be protected. \ 344 If a security incident is able to undermine the functions of \ 345 the management plane, it can be impossible for you to \ 346 recover or stabilize the network. 347 348 """ 349 if categoryname == 'ControlPlane': 350 catname = 'Control plane' 351 catdef = """ 352 Control plane functions consist of the protocols \ 353 and processes that communicate between network devices to move data \ 354 from source to destination. This includes routing protocols such as \ 355 the Border Gateway Protocol, as well as protocols like ICMP and \ 356 the Resource Reservation Protocol (RSVP). 357 358 """ 359 if categoryname == 'DataPlane': 360 catname = 'Data plane' 361 catdef = """ 362 Although the data plane is responsible for \ 363 moving data from source to destination, within the context of \ 364 security, the data plane is the least important of the three planes. \ 365 It is for this reason that when securing a network device it is \ 366 important to protect the management and control planes \ 367 in preference over the data plane. 368 369 """ 370 return '=[' + catname + ']=' + catdef
371 372
373 -def cvss_score(metrics):
374 """Calculate the CVSS score.""" 375 metrics = metrics.split('/') 376 accessvector = str(metrics[0]).split(':')[1] 377 if accessvector == "L": 378 AV = float(0.395) 379 elif accessvector == "A": 380 AV = float(0.646) 381 elif accessvector == "N": 382 AV = float(1.0) 383 else: 384 raise Exception 385 accesscomplexity = str(metrics[1]).split(':')[1] 386 if accesscomplexity == "H": 387 AC = float(0.35) 388 elif accesscomplexity == "M": 389 AC = float(0.61) 390 elif accesscomplexity == "L": 391 AC = float(0.71) 392 else: 393 raise Exception 394 authentication = str(metrics[2]).split(':')[1] 395 if authentication == "N": 396 AU = float(0.704) 397 elif authentication == "S": 398 AU = float(0.56) 399 elif authentication == "M": 400 AU = float(0.45) 401 else: 402 raise Exception 403 confidentialityimpact = str(metrics[3]).split(':')[1] 404 if confidentialityimpact == "N": 405 CI = float(0.0) 406 elif confidentialityimpact == "P": 407 CI = float(0.275) 408 elif confidentialityimpact == "C": 409 CI = float(0.660) 410 else: 411 raise Exception 412 integrityimpact = str(metrics[4]).split(':')[1] 413 if integrityimpact == "N": 414 II = float(0.0) 415 elif integrityimpact == "P": 416 II = float(0.275) 417 elif integrityimpact == "C": 418 II = float(0.660) 419 else: 420 raise Exception 421 availabilityimpact = str(metrics[5]).split(':')[1] 422 if availabilityimpact == "N": 423 AI = float(0.0) 424 elif availabilityimpact == "P": 425 AI = float(0.275) 426 elif availabilityimpact == "C": 427 AI = float(0.660) 428 else: 429 raise Exception 430 impact = 10.41*(1-(1-CI)*(1-II)*(1-AI)) 431 exploitability = 20*AV*AC*AU 432 impacter = set_impacter(impact) 433 basescore = round(float( 434 (0.6 * impact + 0.4 * exploitability -1.5) 435 * impacter),1) 436 score = basescore 437 return score
438
439 -def set_impacter(impact):
440 """Calculate the CVSS impacter value.""" 441 if impact == 0: 442 value = float(0) 443 else: 444 value = float(1.176) 445 return value
446
447 -def snmp_community_complexity(name):
448 """Define if the SNMP community is complex enough.""" 449 if len(name) <= 7: 450 return False 451 else: 452 if name == '<removed>': 453 return True 454 if not re.findall('[0-9]', name): 455 return False 456 else: 457 if not re.findall('[A-Z]', name): 458 return False 459 else: 460 if not re.findall('[a-z]', name): 461 return False 462 else: 463 for c in name: 464 if c in string.punctuation: 465 return True 466 return False
467
468 -def dotted_netmask(netmask):
469 """Convert dotted notation of a /xx netmask.""" 470 bits = 0 471 for i in xrange(32-int(netmask),32): 472 bits |= (1 << i) 473 return "%d.%d.%d.%d" % ( 474 (bits & 0xff000000) >> 24, 475 (bits & 0xff0000) >> 16, 476 (bits & 0xff00) >> 8 , 477 (bits & 0xff) 478 )
479
480 -def netmask_wildcard(netmask):
481 """Convert a dotted netmask to a dotted wildcard mask.""" 482 inversedByte = list() 483 bytes = netmask.split('.') 484 for byte in bytes: 485 inversedByte.append(255 - int(byte)) 486 return "%d.%d.%d.%d" % ( 487 inversedByte[0], 488 inversedByte[1], 489 inversedByte[2], 490 inversedByte[3] 491 )
492
493 -def network_address(address, mask):
494 """Output the network address from an address+mask.""" 495 Addressbytes = address.split('.') 496 Maskbytes = mask.split('.') 497 return "%d.%d.%d.%d" % ( 498 int(Addressbytes[0]) & int(Maskbytes[0]), 499 int(Addressbytes[1]) & int(Maskbytes[1]), 500 int(Addressbytes[2]) & int(Maskbytes[2]), 501 int(Addressbytes[3]) & int(Maskbytes[3]) 502 )
503
504 -def network_reverse_address(address, inversedmask):
505 """Output the network address from an address+wildcard mask.""" 506 Addressbytes = address.split('.') 507 Maskbytes = inversedmask.split('.') 508 count = 0 509 for byte in Maskbytes: 510 Maskbytes[count] = int(byte) + 255 511 count = count + 1 512 return "%d.%d.%d.%d" % ( 513 int(Addressbytes[0]) & int(Maskbytes[0]), 514 int(Addressbytes[1]) & int(Maskbytes[1]), 515 int(Addressbytes[2]) & int(Maskbytes[2]), 516 int(Addressbytes[3]) & int(Maskbytes[3]))
517
518 -def check_std_acl(lines, aclnumber):
519 """Check if the standard ACL is found within the block.""" 520 if __builtin__.ipv4_mgmt_outbound is None: 521 return False 522 acl = 'access-list ' + aclnumber.strip() + ' permit' 523 matchACL = search_string(lines, acl) 524 if matchACL is not None: 525 network = matchACL.split(' ')[3] 526 try: 527 mask = matchACL.split(' ')[4] 528 except IndexError: 529 mask = "0.0.0.0" 530 net = network_reverse_address(network, mask) 531 for entry in __builtin__.ipv4_mgmt_outbound: 532 if net == entry[4]: 533 return True 534 return False
535
536 -def check_extd_acl(lines, aclumber):
537 """Check if the extended ACL is found within the block.""" 538 if __builtin__.ipv4_mgmt_inbound is None: 539 return False 540 acl = 'ip access-list extended ' + aclumber.strip() 541 specificacl = parse_extd_acl(acl) 542 matchACL = search_multi_string(specificacl[0], 'permit') 543 validated= False 544 545 if matchACL is not None: 546 for ace in matchACL: 547 network = ace.split(' ')[2] 548 if network == 'host': 549 mask = "0.0.0.0" 550 net = ace.split(' ')[3] 551 elif network == 'any': 552 pass 553 else: 554 mask = ace.split(' ')[3] 555 net = network_reverse_address(network, mask) 556 557 for entry in __builtin__.ipv4_mgmt_inbound: 558 if net == entry[4]: 559 validated = True 560 else: 561 validated = False 562 return validated
563
564 -def populate_ifaces(lines, interfaces):
565 """Populate a table with each line of an interface configuration.""" 566 ifacecfg = [] 567 recordcfg = False 568 ifaceindex = 0 569 for line in lines: 570 if line.lower().startswith('interface'): 571 ifacename = line.split(' ')[1] 572 ifacecfg.append(interfaces.add_if('interface', ifacename)) 573 recordcfg = True 574 if recordcfg == True and line.startswith('!'): 575 recordcfg = False 576 ifaceindex = ifaceindex + 1 577 elif recordcfg == True and line.startswith('!') == False: 578 if line.lower().startswith('interface') == False: 579 ifacecfg[ifaceindex].configuration.append(line.strip()) 580 return ifacecfg
581
582 -def populate_acl_v4(lines, acls):
583 """Populate a table with each line of a standard IPv4 ACL.""" 584 acl = [] 585 recordcfg = False 586 index = 0 587 for line in lines: 588 if line.lower().startswith('ip access-list'): 589 aclname = line.split(' ')[3] 590 acl.append(acls.add('aclv4', aclname)) 591 recordcfg = True 592 elif line.lower().startswith('access-list'): 593 aclname = line.split(' ')[2] 594 acl.append(acls.add('aclv4', aclname)) 595 recordcfg = True 596 if recordcfg == True and line.startswith('!'): 597 recordcfg = False 598 index = index + 1 599 elif recordcfg == True and line.startswith('!') == False: 600 if line.lower().startswith('ip access-list') == False: 601 acl[index].configuration.append(line.strip()) 602 if line.lower().startswith('access-list') == False: 603 acl[index].configuration.append(line.strip()) 604 return acl
605
606 -def populate_acl_v6(lines, acls):
607 """Populate a table with each line of a standard IPv6 ACL.""" 608 acl = [] 609 recordcfg = False 610 index = 0 611 for line in lines: 612 if line.lower().startswith('ipv6 access-list'): 613 aclName = line.split(' ')[2] 614 acl.append(acls.add('aclv6', aclName)) 615 recordcfg = True 616 if recordcfg == True and line.startswith('!'): 617 recordcfg = False 618 index = index + 1 619 elif recordcfg == True and line.startswith('!') == False: 620 if line.lower().startswith('ipv6 access-list') == False: 621 acl[index].configuration.append(line.strip()) 622 return acl
623