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
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
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 :
33 validatedConfig = 0
34 break
35 if line.startswith('feature') == True :
36 validatedConfig = 0
37 break
38 if validatedConfig == 0:
39 print "This is not an IOS configuration file."
40 exit(1)
41 return
42
55
57 """Convert XML to DATA."""
58 __currentNode__ = None
59 __itemsList__ = None
60
67
94
95 - def get_text(self, node):
96 return node.childNodes[0].nodeValue
98 fsock = open('ciscoObjects.xml')
99 self.xmldoc = parse(fsock)
100 fsock.close()
101
116
118 """Left and right strip a specific line."""
119 strippedLine = line.lstrip().rstrip()
120 return strippedLine
121
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
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
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
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
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
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
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
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
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
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
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
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
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
329 """Print the category banner section."""
330 return '=[ ' + categoryname
331
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
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
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
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
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
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
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
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
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
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
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
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
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