├── ChangeLog ├── Guinevere.py ├── README.md └── static └── G-Checklist ├── G-Checklist.css └── G-Checklist_Template.html /ChangeLog: -------------------------------------------------------------------------------- 1 | 1.2.0 2 | Added --debug command line argument 3 | Changed colors for printing vulnerability criticalities to the terminal 4 | Added gather_assessment_details function to gather additional information about the assessment from Gauntlet 5 | Added new menu item "Generate Assessment JSON File" and associated function generate_assessment_json 6 | 7 | 1.3.0 8 | Added --ports command line argument. Specify the flag to EXCLUDE port information from generated report 9 | Generated output will now include host followed by port (i.e. 192.168.1.10:445/tcp) 10 | -if port is 0/tcp or 0/udp, port information will be excluded 11 | Modified Vulnerability headings to a level 4 header 12 | Modified Vulnerability narrative to be aligned justified 13 | 14 | 1.4.0 15 | Addedd --cvss command line argument. Specify the flag to EXCLUDE CVSS scores from the vulnerability titles 16 | Imported a new library: cvss 17 | Added new get_cvss function 18 | Updated write_multi_vul & write_single_vul to print CVSS score with the header in this format: "CVSS: 10.0 - High" 19 | Added new CVSS column to multi vuln tables -------------------------------------------------------------------------------- /Guinevere.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | #!/usr/bin/env python 3 | 4 | """Guinevere is a tool used to automate security assessment reporting""" 5 | 6 | import MySQLdb, os, docx, argparse, math, netaddr, logging, readline, sys, json 7 | from warnings import filterwarnings, resetwarnings 8 | from docx.enum.text import WD_ALIGN_PARAGRAPH 9 | from cvss import CVSS2 10 | 11 | #Requires MySQL driver, python-mysqldb for Linux. Seems to be installed in Kali 12 | #Requires python-docx library, apt-get update; apt-get install -y python-pip mysql-client;pip install python-docx 13 | #Requires cvsss library; pip install cvss 14 | ### OSX Install Notes: 15 | # sudo su - 16 | # export CFLAGS=-Qunused-arguments 17 | # export CPPFLAGS=-Qunused-arguments 18 | # pip install mysql-python 19 | # pip install python-docx 20 | # pip install netaddr 21 | 22 | ################################################# 23 | # Guinevere Variables # 24 | ################################################# 25 | __author__ = "Russel Van Tuyl" 26 | __license__ = "GPL" 27 | __version__ = "1.4.1" 28 | __maintainer__ = "Russel Van Tuyl" 29 | __email__ = "Russel.VanTuyl@gmail.com" 30 | __status__ = "Development" 31 | G_root = os.path.dirname(os.path.realpath(__file__)) 32 | readline.parse_and_bind('tab: complete') 33 | readline.set_completer_delims('\t') 34 | # logging.basicConfig(stream=sys.stdout, format='%(asctime)s\t%(levelname)s\t%(message)s', 35 | # datefmt='%Y-%m-%d %I:%M:%S %p', level=logging.DEBUG) # Log to STDOUT 36 | logging.basicConfig(filename=os.path.join(G_root, 'Guinevere.log'), format='%(asctime)s\t%(levelname)s\t%(message)s', 37 | datefmt='%Y-%m-%d %I:%M:%S %p', level=logging.DEBUG) # Log to File 38 | ################################################# 39 | #CHANGE TO MATCH YOUR DATABASE 40 | g_ip = "127.0.0.1" # Database IP address 41 | g_p = 3306 # Database Port 42 | g_user = "gauntlet" # Database Username 43 | g_pass = "password" # Database Password 44 | ################################################# 45 | # COLORS # 46 | ################################################# 47 | note = "\033[0;0;33m-\033[0m" 48 | warn = "\033[0;0;31m!\033[0m" 49 | info = "\033[0;0;36mi\033[0m" 50 | question = "\033[0;0;37m?\033[0m" 51 | debug = "\033[0;0;31m[DEBUG]\033[0m" 52 | 53 | #Parse command line arguments 54 | parser = argparse.ArgumentParser() 55 | parser.add_argument('-H', '--db-host', type=str, default=g_ip, help="MySQL Database Host. Default set in script") 56 | parser.add_argument('-U', '--db-user', type=str, default=g_user, help="MySQL Database Username. Default set in script") 57 | parser.add_argument('-P', '--db-pass', type=str, default=g_pass, help="MySQL Database Password. Default set in script") 58 | parser.add_argument('-p', '--db-port', type=str, default=g_p, help="MySQL Database Port. Default set in script") 59 | parser.add_argument('-l', '--lines', type=int, default=10, help="Number of lines to display when selecting an " 60 | "engagement. Default is 10") 61 | parser.add_argument('-A', '--all-vulns', action='store_true', default=False, help="Include all vulnerability headings " 62 | "when there are no associated report " 63 | "narratives") 64 | parser.add_argument('-V', '--all-verb', action='store_true', default=False, help="Include all vureto vulnerability " 65 | "verbiage when there are no " 66 | "associated report narratives") 67 | parser.add_argument('--ports', action='store_false', default=True, help="Exclude port information vulnerability " 68 | "write-up portion of the report") 69 | parser.add_argument('--cvss', action='store_false', default=True, help="Exclude CVSS scores from vulnerability titles") 70 | parser.add_argument('-sC', action='store_false', default=True, help="Exclude Critical-Severity Vulnerabilities") 71 | parser.add_argument('-sH', action='store_false', default=True, help="Exclude High-Severity Vulnerabilities") 72 | parser.add_argument('-sM', action='store_false', default=True, help="Exclude Medium-Severity Vulnerabilities") 73 | parser.add_argument('-sL', action='store_true', default=False, help="Include Low-Severity Vulnerabilities") 74 | parser.add_argument('-sI', action='store_true', default=False, help="Include Informational-Severity Vulnerabilities") 75 | parser.add_argument('-aD', '--assessment-date', action='store_true', default=False, help='Include the date when ' 76 | 'selecting an assessment ' 77 | 'to report on') 78 | parser.add_argument('-T', '--tool-output', action='store_false', default=True, help="Exclude Tool Output When Printing " 79 | "G-Checklist") 80 | parser.add_argument('--debug', action='store_true', default=False, help="Enable debug output to console") 81 | args = parser.parse_args() 82 | 83 | 84 | def get_assessment(info_string_for_user): 85 | """Connects to the assessment database and prompts user to select an engagement""" 86 | 87 | logging.info('Entering the get_assessment function') 88 | db = MySQLdb.connect(host=args.db_host, user=args.db_user, passwd=args.db_pass, port=args.db_port) 89 | gauntlet=db.cursor() 90 | gauntlet.execute("""show databases""") 91 | all_available_databases = gauntlet.fetchall() 92 | gauntlet.close() 93 | discovered_gauntlet_databases = [] 94 | os.system('clear') 95 | banner() 96 | 97 | # number of databases to print at a time 98 | database_print_limit_counter = args.lines 99 | 100 | # add all databases that start with 'gauntlet_' to a list 101 | for discovered_db in all_available_databases: 102 | if discovered_db[0].startswith('gauntlet'): 103 | discovered_gauntlet_databases.append(discovered_db[0].replace("gauntlet_", '')) 104 | 105 | #Print engagements to screen and have user choose one 106 | # Counter for number of engagements printed to the screen 107 | printed_engagement_counter = 0 108 | z = False 109 | # Counter for the number of engagements to display at a time 110 | eng_display_limit_counter = database_print_limit_counter 111 | while z != True: 112 | if (printed_engagement_counter <= eng_display_limit_counter) and (printed_engagement_counter < len(discovered_gauntlet_databases)): 113 | if args.assessment_date: 114 | SDate = db_query('select value FROM engagement_details WHERE `key`="Start Date"', discovered_gauntlet_databases[printed_engagement_counter]) 115 | if SDate is not None: 116 | print "[" + str(printed_engagement_counter) + "]" + discovered_gauntlet_databases[printed_engagement_counter] + "\t" + SDate[0][0] 117 | else: 118 | print "[" + str(printed_engagement_counter) + "] " + discovered_gauntlet_databases[printed_engagement_counter] 119 | printed_engagement_counter += 1 120 | else: 121 | print "[99] More..." 122 | user_selection_string = raw_input("\n[" + question +"]Please select " + info_string_for_user + ": ") 123 | try: 124 | if ((user_selection_string == "99") or (user_selection_string == "")): 125 | if printed_engagement_counter == len(discovered_gauntlet_databases): 126 | printed_engagement_counter = 0 127 | eng_display_limit_counter = database_print_limit_counter 128 | else: 129 | eng_display_limit_counter = eng_display_limit_counter + database_print_limit_counter 130 | os.system('clear') 131 | banner() 132 | elif user_selection_string == "Q" or user_selection_string == "q": 133 | exit() 134 | elif discovered_gauntlet_databases[int(user_selection_string)]: 135 | z = True 136 | else: 137 | pass 138 | except: 139 | os.system('clear') 140 | banner() 141 | print "["+warn+"]ERROR: " + user_selection_string + " is not a valid option. Try again" 142 | printed_engagement_counter = 0 143 | eng_display_limit_counter = database_print_limit_counter 144 | os.system('clear') 145 | logging.info(discovered_gauntlet_databases[int(user_selection_string)] + " assessment selected by user") 146 | 147 | return discovered_gauntlet_databases[int(user_selection_string)] 148 | 149 | 150 | def get_crosstable(assessment): 151 | """Select the which assessment crosstable to use""" 152 | 153 | logging.info('Entering the get_crosstable function') 154 | chosen_crosstable = "" 155 | list_of_crosstables = () 156 | tries = 0 157 | #Find all the crosstables for the assessment 158 | while not list_of_crosstables: 159 | try: 160 | if tries < 100: 161 | q = """SELECT DISTINCT table_id from cross_data_nva""" 162 | list_of_crosstables = db_query(q, assessment) 163 | else: 164 | print "Its broken try again" 165 | break 166 | except: 167 | tries = tries+1 168 | 169 | while not chosen_crosstable: 170 | 171 | # If there is more than 1 crosstable, have the user choose 172 | if len(list_of_crosstables) > 1: 173 | list_number = 0 174 | for crosstable in list_of_crosstables: 175 | print "[" + str(list_number) + "]",crosstable[0] 176 | list_number += 1 177 | try: 178 | user_choice = raw_input("\nWhich Crosstable would you like to use: ") 179 | chosen_crosstable = list_of_crosstables[int(user_choice)] 180 | except: 181 | os.system('clear') 182 | banner() 183 | print "["+warn+"]Error: please try again" 184 | 185 | # Otherwise choose for the user 186 | else: 187 | chosen_crosstable = list_of_crosstables[0] 188 | 189 | os.system('clear') 190 | banner() 191 | logging.info(str(chosen_crosstable[0]) + " crosstable selected") 192 | return chosen_crosstable[0] 193 | 194 | 195 | def assessment_vulns(assessment, crosstable): 196 | """Builds a list of the assessment vulnerabilities""" 197 | 198 | vulns = [] 199 | plugins = "" 200 | #Import data from gauntlet db for the selected crosstable 201 | hosts = db_query("select * from cross_data_nva WHERE table_id = '%s'" % (crosstable), assessment) 202 | 203 | # Vureto Vuln ID is in spot 2 of the tuple returned by i in the following loop 204 | Vureto_Vuln_ID_Location = 2 205 | 206 | for i in hosts: 207 | Vureto_Vuln_ID = i[Vureto_Vuln_ID_Location] 208 | if ((Vureto_Vuln_ID.startswith('Nessus') or Vureto_Vuln_ID.startswith('Netsparker') or Vureto_Vuln_ID.startswith('Acunetix') 209 | or Vureto_Vuln_ID.startswith('BurpSuite')) and Vureto_Vuln_ID not in plugins): 210 | plugins += Vureto_Vuln_ID 211 | else: 212 | vulns.append(Vureto_Vuln_ID) 213 | if plugins != "": 214 | pass 215 | return vulns 216 | 217 | 218 | def assessment_report(vulnerability_id_info_mapping_list): 219 | """Builds a unique list of Report IDs for the selected assessment and crosstable""" 220 | 221 | logging.info('Entered assessment_report function') 222 | temp = [] 223 | for vulnerability_id in vulnerability_id_info_mapping_list: 224 | if args.debug: 225 | logging.info('Vulnerability: ' + vulnerability_id + " - " + vulnerability_id_info_mapping_list[vulnerability_id]['vuln_title']) 226 | if vulnerability_id_info_mapping_list[vulnerability_id]['vuln_report_id'] is not None: 227 | temp.append(vulnerability_id_info_mapping_list[vulnerability_id]['vuln_report_id']) 228 | else: 229 | pass 230 | logging.info('Leaving assessment_report_function') 231 | return set(temp) 232 | 233 | 234 | def get_vulns(vuln_IDs, assessment, crosstable): 235 | """Build dictionary containing the assessment vulnerabilities and their associated information""" 236 | 237 | logging.info('Entered get_vulns function...') 238 | vulns = {} 239 | plugins = "" 240 | tools = ['Nessus', 'Netsparker', 'Acunetix', 'BurpSuite', 'Nmap', 'Nikto', 'dirb'] # names of tools to ignore 241 | db = MySQLdb.connect(host=args.db_host, user=args.db_user, passwd=args.db_pass, port=args.db_port, db='GauntletData') 242 | filterwarnings('ignore', category = MySQLdb.Warning) # Disable MySQL Warnings 243 | 244 | # Variable for progress bar 245 | countvulnVar = 0 246 | 247 | # vuln_IDs is an array of vuln id's created in assessment_vulns function 248 | for vuln_id in vuln_IDs: 249 | 250 | # Create a progress bar 251 | countvulnVar = countvulnVar + 1.0 252 | progress = str(countvulnVar / len(vuln_IDs) * 100) 253 | print "\r\t[" + note + "]Querying Database For Vulnerability Information:", progress[:5] + "%", 254 | 255 | #TODO to remove "Nessus 1111" entries 256 | 257 | if vuln_id.split()[0] in tools: 258 | if vuln_id not in plugins and vuln_id is "": 259 | plugins += "\t["+warn+"]" + vuln_id + " plugin needs to be added to your Gauntlet database" 260 | elif vuln_id not in plugins: 261 | plugins += "\n\t["+warn+"]" + vuln_id + " plugin needs to be added to your Gauntlet database" 262 | 263 | # Get vulnerability verbage 264 | else: 265 | gauntlet = db.cursor() 266 | gauntlet.execute("""select title, description, solution, report_id from vulns WHERE gnaat_id=%s""", (vuln_id,)) 267 | temp = gauntlet.fetchone() 268 | gauntlet.close() 269 | if temp[3] is not None: 270 | 271 | vulns[vuln_id] = {'vuln_id': vuln_id, 'vuln_title': temp[0], 'vuln_desc': temp[1], 'vuln_sol': temp[2], 'vuln_report_id': int(temp[3])} 272 | else: 273 | vulns[vuln_id] = {'vuln_id': vuln_id, 'vuln_title': temp[0], 'vuln_desc': temp[1], 'vuln_sol': temp[2], 'vuln_report_id': temp[3]} 274 | 275 | db2 = MySQLdb.connect(host=args.db_host, user=args.db_user, passwd=args.db_pass, port=args.db_port, db='gauntlet_'+ assessment) 276 | #Add all hosts with the associated vulnerability to the rpt dictionary 277 | print "" 278 | countvulnVar = 0 279 | 280 | #Determine if cross_data_nva has a 'port' column 281 | portColumn = False 282 | gauntlet=db2.cursor() 283 | gauntlet.execute("""SHOW COLUMNS FROM cross_data_nva FROM gauntlet_%s;""" % assessment) 284 | columns = gauntlet.fetchall() 285 | gauntlet.close 286 | for c in columns: 287 | if c[0] == 'port': 288 | portColumn = True 289 | 290 | for vuln_id in vuln_IDs: 291 | 292 | # Create progress bar 293 | countvulnVar = countvulnVar + 1.0 294 | progress = str(countvulnVar / len(vuln_IDs) * 100) 295 | print "\r\t[" + note + "]Querying Database For Affected Hosts:", progress[:5] + "%", 296 | 297 | # Ignore id's in tool list 298 | if vuln_id.split()[0] in tools: 299 | pass 300 | else: 301 | gauntlet=db2.cursor() 302 | if args.ports and portColumn: 303 | gauntlet.execute( 304 | """SELECT host, port, protocol FROM cross_data_nva WHERE cross_data_nva.table_id =%s 305 | AND vuln_id=%s AND (s1='Y' or s2='Y' or s3='Y' or s4='Y' or s5='Y')""", 306 | (crosstable,vuln_id)) 307 | else: 308 | gauntlet.execute( 309 | """SELECT host FROM cross_data_nva WHERE cross_data_nva.table_id =%s AND vuln_id=%s 310 | AND (s1='Y' or s2='Y' or s3='Y' or s4='Y' or s5='Y')""", 311 | (crosstable, vuln_id)) 312 | temp2 = gauntlet.fetchall() 313 | gauntlet.close() 314 | #print vuln[j]['vuln_title'], temp2 #DEBUG 315 | vulns[vuln_id].update({'vuln_hosts': temp2}) 316 | 317 | #Determine the rank of the vulnerability 318 | print "" 319 | countvulnVar = 0 320 | for vuln_id in vuln_IDs: 321 | 322 | # Create progress bar 323 | countvulnVar = countvulnVar + 1.0 324 | progress = str(countvulnVar / len(vuln_IDs) * 100) 325 | print "\r\t[" + note + "]Querying Database For Vulnerability Rank:", progress[:5] + "%", 326 | 327 | # Craft assessment string for SQL query 328 | if assessment == "GauntletData": 329 | assessment_name = assessment 330 | else: 331 | assessment_name = "gauntlet_" + assessment 332 | 333 | # Ignore ids in tool list 334 | if vuln_id.split()[0] in tools: 335 | pass 336 | # Select severity values from cross_tables 337 | else: 338 | temp4 = db_query("""SELECT s1, s2, s3, s4, s5 FROM cross_data_nva WHERE table_id ='%s' AND vuln_id = %s;""" % (crosstable, vuln_id), assessment) 339 | severities = [] 340 | for value in temp4: 341 | if value[0] is 'Y' and ('Critical' not in severities): 342 | severities.append('Critical') 343 | elif value[1] is 'Y' and ('High' not in severities): 344 | severities.append('High') 345 | elif value[2] is 'Y' and ('Medium' not in severities): 346 | severities.append('Medium') 347 | elif value[3] is 'Y' and ('Low' not in severities): 348 | severities.append('Low') 349 | elif value[4] is 'Y' and ('Informational' not in severities): 350 | severities.append('Informational') 351 | else: 352 | if None in severities: 353 | pass 354 | else: 355 | severities.append(None) 356 | 357 | # Update vuln_rating to the highest discovered severity 358 | if 'Critical' in severities: 359 | vulns[vuln_id].update({'vuln_rating': 'Critical'}) 360 | elif 'High' in severities: 361 | vulns[vuln_id].update({'vuln_rating': 'High'}) 362 | elif 'Medium' in severities: 363 | vulns[vuln_id].update({'vuln_rating': 'Medium'}) 364 | elif 'Low' in severities: 365 | vulns[vuln_id].update({'vuln_rating': 'Low'}) 366 | elif 'Informational' in severities: 367 | vulns[vuln_id].update({'vuln_rating': 'Informational'}) 368 | else: 369 | vulns[vuln_id].update({'vuln_rating': None}) 370 | 371 | if plugins != "": 372 | print "%s" %(plugins,), 373 | print "" 374 | resetwarnings() #Re-enable MySQL Warnings 375 | return vulns 376 | 377 | 378 | def get_report(report_record_IDs, vuln_ID_info_mapping): 379 | """Build a dictionary containing all of the reporting information""" 380 | logging.info("Entering get_report function") 381 | 382 | rpt = {} 383 | 384 | # change to GauntletData after dev/or vureto for dev 385 | db = MySQLdb.connect(host=args.db_host, user=args.db_user, passwd=args.db_pass, port=args.db_port, db='GauntletData') 386 | 387 | for report_record_ID in report_record_IDs: 388 | gauntlet = db.cursor() 389 | gauntlet.execute("""select title, identification, explanation, impact, recommendation from report 390 | WHERE report_id=%s""" , (report_record_ID,)) 391 | temp = gauntlet.fetchone() 392 | gauntlet.close() 393 | rpt[report_record_ID] = {'report_id': report_record_ID, 'report_title': temp[0], 'report_identification': temp[1], 394 | 'report_explanation': temp[2], 'report_impact': temp[3], 'report_recommendation': temp[4]} 395 | 396 | # Add all vulnerabilities with this report ID to the dictionary 397 | for vuln_ID in vuln_ID_info_mapping: 398 | if vuln_ID_info_mapping[vuln_ID]['vuln_report_id'] is not None: 399 | if 'vulns' in rpt[vuln_ID_info_mapping[vuln_ID]['vuln_report_id']]: 400 | rpt[vuln_ID_info_mapping[vuln_ID]['vuln_report_id']]['vulns'][vuln_ID_info_mapping[vuln_ID]['vuln_id']] = vuln_ID_info_mapping[vuln_ID] 401 | else: 402 | rpt[vuln_ID_info_mapping[vuln_ID]['vuln_report_id']]['vulns'] = {vuln_ID_info_mapping[vuln_ID]['vuln_id']: vuln_ID_info_mapping[vuln_ID]} 403 | else: 404 | pass 405 | 406 | # Determine the highest severity level and set it for the reporting record 407 | for record in rpt: 408 | r = [] 409 | for vulnerability in rpt[record]['vulns']: 410 | r.append(rpt[record]['vulns'][vulnerability]['vuln_rating']) 411 | if 'Critical' in r: 412 | rpt[record]['report_rating'] = 'Critical' 413 | continue 414 | elif 'High' in r: 415 | rpt[record]['report_rating'] = 'High' 416 | continue 417 | elif 'Medium' in r: 418 | rpt[record]['report_rating'] = 'Medium' 419 | continue 420 | elif 'Low' in r: 421 | rpt[record]['report_rating'] = 'Low' 422 | continue 423 | elif 'Informational' in r: 424 | rpt[record]['report_rating'] = 'Informational' 425 | continue 426 | else: 427 | rpt[record]['report_rating'] = None 428 | logging.info('Leaving get_report Function') 429 | return rpt 430 | 431 | 432 | def banner(): 433 | """Guinevere's banner""" 434 | #Art retrieved from http://www.oocities.org/spunk1111/women.htm 435 | 436 | print """ ,,,_""" 437 | print """ .' `'. ################################################""" 438 | print " / ____ \\# Guinevere v"+__version__+" #" 439 | print " | .`_ _\/# #" 440 | print " / ) a a| # Automated Security Assessment Reporting #" 441 | print " / ( > | ################################################" 442 | print """ ( ) ._ / """ 443 | print " ) _/-.__.'`\\" 444 | print """( .-'`-. \__ )""" 445 | print """ `/ `-./ `. """ 446 | print " | \ \ \\" 447 | print " | \ \ \ \\" 448 | print " |\ `. / / \\" 449 | print "_________________________________________________________________" 450 | 451 | 452 | def int_to_string(i): 453 | """Converts an integer to its spelled out version; Used in reporting narratives""" 454 | 455 | s = { 0: "", #Dictionary of integers to spelled out words 456 | 1: "one", 457 | 2: "two", 458 | 3: "three", 459 | 4: "four", 460 | 5: "five", 461 | 6: "six", 462 | 7: "seven", 463 | 8: "eight", 464 | 9: "nine", 465 | 10: "ten", 466 | 11: "eleven", 467 | 12: "twelve", 468 | 13: "thirteen", 469 | 14: "fourteen", 470 | 15: "fifteen", 471 | 16: "sixteen", 472 | 17: "seventeen", 473 | 18: "eighteen", 474 | 19: "nineteen", 475 | 20: "twenty", 476 | 30: "thirty", 477 | 40: "forty", 478 | 50: "fifty", 479 | 60: "sixty", 480 | 70: "seventy", 481 | 80: "eighty", 482 | 90: "ninety" 483 | } 484 | 485 | #break i into ones, tens, hundreds, thousands and then build string 486 | #ONES 487 | if len(str(i)) is 1 and str(i) is "1": #Spelling for host as opposed to hosts 488 | return s[i] +" (" + str(i) + ") host" 489 | elif len(str(i)) is 1 and str(i) is not "1": #Spelling for single digit hosts 490 | return s[i] +" (" + str(i) + ") hosts" 491 | #TENS 492 | elif len(str(i)) is 2 and str(i).startswith("1") : #To grab spelling for 11 through 19 493 | return s[i]+" (" + str(i) + ") hosts" 494 | elif len(str(i)) is 2 and not str(i).startswith("1") and not str(i).endswith("0"): #To grab spelling for 20 through 99 where the number doesn't end in 0 495 | return s[(i/10)*10]+"-"+s[i -((i/10)*10)] +" (" + str(i) + ") hosts" 496 | elif len(str(i)) is 2 and not str(i).startswith("1") and str(i).endswith("0"): #To grab spelling for 20 through 99 where the number doesn't end in 0 497 | return s[(i/10)*10]+s[i -((i/10)*10)] +" (" + str(i) + ") hosts" 498 | #HUNDREDS 499 | elif len(str(i)) is 3 and "0" not in str(i): #to grab spelling for 100's where the number doesn't have a zero 500 | return s[(i/100)]+"-hundred "+s[((i -((i/100)*100))/10)*10]+"-"+s[i-(((i/100)*100)+((i -((i/100)*100))/10)*10)] +" (" + str(i) + ") hosts" 501 | elif len(str(i)) is 3 and not str(i).endswith("0") and "0" in str(i): #to grab spelling for 100's where the 10s place is 0 502 | return s[(i/100)]+"-hundred"+s[((i -((i/100)*100))/10)*10]+" "+s[i-(((i/100)*100)+((i -((i/100)*100))/10)*10)] +" (" + str(i) + ") hosts" 503 | elif len(str(i)) is 3 and str(i).endswith("0"): #to grab spelling for 100's where the number ends in 0 504 | return s[(i/100)]+"-hundred"+s[((i -((i/100)*100))/10)*10]+s[i-(((i/100)*100)+((i -((i/100)*100))/10)*10)] +" (" + str(i) + ") hosts" 505 | else: 506 | return "ERROR, was not able to return the number of host(s)" 507 | 508 | 509 | def ip_sort(hosts): 510 | """Put the provided list of tuples IP addresses into order""" 511 | 512 | ips = [] 513 | hostnames = [] 514 | for ip in hosts: 515 | if unicode(ip[0].split('.')[0]).isnumeric(): 516 | ips.append(netaddr.IPAddress(ip[0])) # isnumeric only works with Unicode; checking for IP 517 | # elseif: ip[0].isalpha(): # Checking for when a hostname is used instead of an IP 518 | else: 519 | hostnames.append(ip[0]) 520 | 521 | ips = sorted(ips) 522 | sorted_hosts = [] 523 | for i in ips: # Add IPs 524 | sorted_hosts.append(str(i)) 525 | for i in hostnames: # Add Hostnames 526 | sorted_hosts.append(str(i)) 527 | return sorted_hosts 528 | 529 | 530 | def ip_sort_list(hosts): 531 | """Put the provided list IP addresses into order""" 532 | # TODO normalize both ip_sort functions to only use one data type (list or tuple) 533 | ips = [] 534 | hostnames = [] 535 | for ip in hosts: 536 | if unicode(ip.split('.')[0]).isnumeric(): 537 | ips.append(netaddr.IPAddress(ip)) # isnumeric only works with Unicode; checking for IP 538 | else: 539 | hostnames.append(ip) 540 | 541 | ips = sorted(ips) 542 | sorted_hosts = [] 543 | for i in ips: # Add IPs 544 | sorted_hosts.append(str(i)) 545 | for i in hostnames: # Add Hostnames 546 | sorted_hosts.append(str(i)) 547 | return sorted_hosts 548 | 549 | 550 | def generate_hosts_table(file, ass): 551 | """Build a list of assessment interesting hosts; hosts with atleast one TCP or UDP port open.""" 552 | 553 | hosts = {} 554 | file.add_page_break() 555 | logging.info('Entering the generate_hosts_table function') 556 | print "["+note+"]Generating Interesting Hosts Table" 557 | # Build dictionary of host IDs and IPs from gauntlet's 'hosts' table 558 | engagement = db_query("""SELECT value FROM gauntlet_%s.engagement_details WHERE engagement_details.key = 'Engagement Task 1'""" % (ass), ass) 559 | if engagement: 560 | if 'Internal' in engagement[0][0]: 561 | temp = db_query("""SELECT host_id, ip_address, machine_name from hosts""", ass) 562 | else: 563 | temp = db_query("""SELECT host_id, ip_address, fqdn from hosts""", ass) 564 | else: 565 | temp = db_query("""SELECT host_id, ip_address, machine_name from hosts""", ass) 566 | temp2 = db_query("""SELECT host_id, port, protocol from ports""", ass) 567 | for i in temp: 568 | tcp = [] 569 | udp = [] 570 | for j in temp2: 571 | if (j[0] == i[0]) and (j[1] != "0"): 572 | if j[2] == 'tcp': 573 | tcp.append(j[1]) 574 | elif j[2] == 'udp': 575 | udp.append(j[1]) 576 | else: 577 | pass 578 | else: 579 | pass 580 | hosts[i[0]] = {'IP': i[1], 'Name': i[2], 'TCP': tcp, 'UDP': udp} 581 | x = 0 # Number of interesting hosts counter 582 | for host in hosts: 583 | if (len(hosts[host]['TCP']) > 0) or (len(hosts[host]['UDP']) > 0): 584 | x += 1 585 | else: 586 | pass 587 | logging.info("["+info+"]"+str(x) + " Interesting Hosts") 588 | print "\t["+info+"]"+str(x) + " Interesting Hosts" 589 | file.add_heading(str(x) + ' Interesting Host(s) List') 590 | table = file.add_table(rows=1, cols=4) 591 | hdr_cells = table.rows[0].cells 592 | hdr_cells[0].text = 'IP Address' 593 | hdr_cells[1].text = 'Hostname' 594 | hdr_cells[2].text = 'Open TCP Port(s)' 595 | hdr_cells[3].text = 'Open UDP Port(s)' 596 | table.style = 'Medium Grid 1 Accent 1' 597 | 598 | # Build a list of sorted IPs 599 | sorted_hosts = [] 600 | for host in hosts: 601 | sorted_hosts.append(hosts[host]['IP']) 602 | sorted_hosts = ip_sort_list(sorted_hosts) 603 | 604 | for ip in sorted_hosts: 605 | for k in hosts: 606 | if hosts[k]['IP'] == ip: 607 | if (len(hosts[k]['TCP']) > 0) or (len(hosts[k]['UDP']) > 0): 608 | x += 1 609 | row_cells = table.add_row().cells 610 | row_cells[0].text = hosts[k]['IP'] 611 | if len(hosts[k]['Name']) > 0: 612 | row_cells[1].text = hosts[k]['Name'] 613 | else: 614 | row_cells[1].text = "---" 615 | if len(hosts[k]['TCP']) > 0: 616 | row_cells[2].text = str(hosts[k]['TCP']).lstrip('[').rstrip(']').replace("'", "") 617 | else: 618 | row_cells[2].text = "---" 619 | if len(hosts[k]['UDP']) > 0: 620 | row_cells[3].text = str(hosts[k]['UDP']).lstrip('[').rstrip(']').replace("'", "") 621 | else: 622 | row_cells[3].text = "---" 623 | else: 624 | pass 625 | 626 | return file 627 | 628 | 629 | def generate_vuln_list(report_docx, assessment, vuln_dictionary_data): 630 | """Build the bullet list of vulnerabilities used in the executive summary""" 631 | 632 | logging.info("Entering the generate_vuln_list function") 633 | engagement = db_query("""SELECT value FROM gauntlet_%s.engagement_details WHERE engagement_details.key = 'Engagement Task 1'""" % (assessment), assessment) 634 | 635 | if engagement: 636 | report_docx.add_heading(str(engagement[0][0]) + ' NVA/PT') 637 | else: 638 | report_docx.add_heading('NVA/PT') 639 | 640 | def writeBullet(s, h): 641 | n = s.find('[n]') + 3 # Find '[n]' and add three to account for the length of '[n]' 642 | s = s.replace(s[0:n], int_to_string(h)) 643 | s = s.rstrip('\n') 644 | s = s.rstrip('\t') 645 | s = s.rstrip() 646 | s = s.rstrip('.') 647 | s = s[0:1].upper() + s[1:] 648 | report_docx.add_paragraph(s, style='List Bullet') 649 | 650 | logging.info("Writing bullets based on criticality in the generate_vuln_list function") 651 | for i in vuln_dictionary_data: 652 | if args.debug: 653 | print "[" + info + "]%s" % vuln_dictionary_data[i] 654 | 655 | # Check to see if is a multi vuln report item 656 | if len(vuln_dictionary_data[i]['vulns']) > 1: 657 | h = 0 658 | for j in vuln_dictionary_data[i]['vulns']: 659 | h += len(vuln_dictionary_data[i]['vulns'][j]['vuln_hosts']) 660 | if args.sC and vuln_dictionary_data[i]['report_rating'] == 'Critical': 661 | writeBullet(vuln_dictionary_data[i]['report_identification'], h) 662 | elif args.sH and vuln_dictionary_data[i]['report_rating'] == 'High': 663 | writeBullet(vuln_dictionary_data[i]['report_identification'], h) 664 | elif args.sM and vuln_dictionary_data[i]['report_rating'] == 'Medium': 665 | writeBullet(vuln_dictionary_data[i]['report_identification'], h) 666 | elif args.sL and vuln_dictionary_data[i]['report_rating'] == 'Low': 667 | writeBullet(vuln_dictionary_data[i]['report_identification'], h) 668 | elif args.sI and vuln_dictionary_data[i]['report_rating'] == 'Informational': 669 | writeBullet(vuln_dictionary_data[i]['report_identification'], h) 670 | elif vuln_dictionary_data[i]['report_rating'] is None: 671 | print "\t[" + note + "]" + vuln_dictionary_data[i]['report_title'] + " has no affected hosts" 672 | else: 673 | pass 674 | 675 | else: 676 | for j in vuln_dictionary_data[i]['vulns']: 677 | if args.sC and vuln_dictionary_data[i]['report_rating'] == 'Critical': 678 | writeBullet(vuln_dictionary_data[i]['report_identification'], len(vuln_dictionary_data[i]['vulns'][j]['vuln_hosts'])) 679 | elif args.sH and vuln_dictionary_data[i]['report_rating'] == 'High': 680 | writeBullet(vuln_dictionary_data[i]['report_identification'], len(vuln_dictionary_data[i]['vulns'][j]['vuln_hosts'])) 681 | elif args.sM and vuln_dictionary_data[i]['report_rating'] == 'Medium': 682 | writeBullet(vuln_dictionary_data[i]['report_identification'], len(vuln_dictionary_data[i]['vulns'][j]['vuln_hosts'])) 683 | elif args.sL and vuln_dictionary_data[i]['report_rating'] == 'Low': 684 | writeBullet(vuln_dictionary_data[i]['report_identification'], len(vuln_dictionary_data[i]['vulns'][j]['vuln_hosts'])) 685 | elif args.sI and vuln_dictionary_data[i]['report_rating'] == 'Informational': 686 | writeBullet(vuln_dictionary_data[i]['report_identification'], len(vuln_dictionary_data[i]['vulns'][j]['vuln_hosts'])) 687 | elif vuln_dictionary_data[i]['report_rating'] is None: 688 | print "\t[" + note + "]" + vuln_dictionary_data[i]['report_title'] + " has no affected hosts" 689 | else: 690 | pass 691 | 692 | return report_docx 693 | 694 | 695 | def db_query(q, assessment): 696 | """General use function used for querying the assessment database""" 697 | 698 | if assessment == "GauntletData": 699 | assessment2 = assessment 700 | else: 701 | assessment2 = "gauntlet_" + assessment 702 | 703 | db = MySQLdb.connect(host=args.db_host, user=args.db_user, passwd=args.db_pass, port=args.db_port, db=assessment2) 704 | try: 705 | gauntlet=db.cursor() 706 | gauntlet.execute(q) 707 | recordset = gauntlet.fetchall() 708 | gauntlet.close() 709 | return recordset 710 | except: 711 | print "There was an error performing the following query: " 712 | print q 713 | 714 | 715 | def save_report(file, ass): 716 | """Save the generated assessment report""" 717 | out_dir = get_path() 718 | guinevere_file = os.path.join(out_dir, "Guinevere_"+ass+".docx") 719 | file.save(guinevere_file) 720 | print "["+warn+"]Report saved to: " + guinevere_file 721 | raw_input("["+question+"]Press enter to continue...") 722 | main_menu() 723 | 724 | 725 | def retest(): 726 | """Create a report for a retest of an assessment""" 727 | 728 | os.system('clear') 729 | banner() 730 | print "Retrieving available assessments..." 731 | 732 | # Collect data from the original assessment 733 | original_assessment = get_assessment("the original assessment") 734 | banner() 735 | original_crosstable = get_crosstable(original_assessment) 736 | print "["+note+"]Gathering original assessment vulnerability IDs..." 737 | original_vID = assessment_vulns(original_assessment, original_crosstable) 738 | print "["+note+"]Gathering original assessment vulnerability dataset..." 739 | original_vuln = get_vulns(original_vID, original_assessment, original_crosstable) 740 | 741 | # Collect data from the retest 742 | retest_assessment = get_assessment("the retest assessment") 743 | banner() 744 | retest_crosstable = get_crosstable(retest_assessment) 745 | print "["+note+"]Gathering retest vulnerability IDs..." 746 | retest_vID = assessment_vulns(retest_assessment, retest_crosstable) 747 | print "["+note+"]Gathering retest vulnerability dataset..." 748 | retest_vuln = get_vulns(retest_vID, retest_assessment, retest_crosstable) 749 | 750 | # Create the report stub 751 | retest_report = docx.Document() 752 | retest_report.add_heading(original_assessment+' Retest Results') 753 | 754 | retest = {} # Dictionary to hold retest data 755 | 756 | for i in original_vuln: 757 | if original_vuln[i]['vuln_rating'] is not None and original_vuln[i]['vuln_rating'] is not "Informational": 758 | retest[i] = {'vuln_id': i, 'vuln_title': original_vuln[i]['vuln_title'], 'vuln_rating': original_vuln[i]['vuln_rating'], 'total_orig': len(set(original_vuln[i]['vuln_hosts']))} 759 | if i in retest_vuln: 760 | o = set(original_vuln[i]['vuln_hosts']) #Original 761 | r = set(retest_vuln[i]['vuln_hosts']) #Retest 762 | l = o - r #Leftover, fixed hosts 763 | b = [] # List of hosts from the original retest that are found in the retest 764 | 765 | for x in o: # For each host in the original assessment, check to see if it is in the retest assessment 766 | if x in r: 767 | b.append(x) 768 | if len(b) == 0: 769 | print "\t["+note+"]" + original_vuln[i]['vuln_title'] + " - Remediated" 770 | retest[i].update({'status': 'Remediated'}) 771 | elif len(b) == len(o): 772 | print "\t["+warn+"]" + original_vuln[i]['vuln_title'] + " - Not Remediated" 773 | retest[i].update({'status': 'Not Remediated'}) 774 | retest[i].update({'v_hosts': o}) #Hosts Still Vulnerable, contributed by Zach 775 | else: 776 | print "\t["+info+"]" + original_vuln[i]['vuln_title'] + \ 777 | " - Partially Remediated (Still vulnerable: " + str(len(b)) + ")" 778 | retest[i].update({'status': 'Partially Remediated'}) 779 | retest[i].update({'v_hosts': b})#Hosts still vulnerable 780 | retest[i].update({'f_hosts': l}) #Fixed hosts 781 | else: 782 | print "\t["+note+"]" + original_vuln[i]['vuln_title'] + " - Remediated" 783 | retest[i].update({'status': 'Remediated'}) 784 | 785 | # Build Status Table 786 | retest_report.add_heading('Vulnerability Status') 787 | status_table = retest_report.add_table(rows=1, cols=3) 788 | status_table.style = 'Medium Grid 1 Accent 1' 789 | hdr_cells = status_table.rows[0].cells 790 | hdr_cells[0].text = 'Severity' 791 | hdr_cells[1].text = 'Vulnerability' 792 | hdr_cells[2].text = 'Status' 793 | 794 | # Add Critical first 795 | for i in retest: 796 | if retest[i]['vuln_rating'] is 'Critical': 797 | row_cells = status_table.add_row().cells 798 | row_cells[0].text = retest[i]['vuln_rating'] 799 | row_cells[1].text = retest[i]['vuln_title'] 800 | row_cells[2].text = retest[i]['status'] 801 | 802 | # Add High second 803 | for i in retest: 804 | if retest[i]['vuln_rating'] is 'High': 805 | row_cells = status_table.add_row().cells 806 | row_cells[0].text = retest[i]['vuln_rating'] 807 | row_cells[1].text = retest[i]['vuln_title'] 808 | row_cells[2].text = retest[i]['status'] 809 | 810 | # Add Medium third 811 | for i in retest: 812 | if retest[i]['vuln_rating'] is 'Medium': 813 | row_cells = status_table.add_row().cells 814 | row_cells[0].text = retest[i]['vuln_rating'] 815 | row_cells[1].text = retest[i]['vuln_title'] 816 | row_cells[2].text = retest[i]['status'] 817 | 818 | # Add Low last 819 | for i in retest: 820 | if retest[i]['vuln_rating'] is 'Low': 821 | row_cells = status_table.add_row().cells 822 | row_cells[0].text = retest[i]['vuln_rating'] 823 | row_cells[1].text = retest[i]['vuln_title'] 824 | row_cells[2].text = retest[i]['status'] 825 | 826 | # Build Still Vulnerable Hosts Table 827 | retest_report.add_heading('Hosts Still Vulnerable') 828 | vulnerable_table = retest_report.add_table(rows=1, cols=3) 829 | vulnerable_table.style = 'Medium Grid 1 Accent 1' 830 | hdr_cells = vulnerable_table.rows[0].cells 831 | hdr_cells[0].text = 'Severity' 832 | hdr_cells[1].text = 'Vulnerability' 833 | hdr_cells[2].text = 'Hosts' 834 | 835 | #Criticals 836 | for i in retest: 837 | # "and retest[i]['vuln_rating'] is not 'Informational' and len(retest[i]['v_hosts']) > 0" Contriubted by Zach 838 | if 'v_hosts' in retest[i] and retest[i]['vuln_rating'] is not 'Informational' and len(retest[i]['v_hosts']) > 0: 839 | if retest[i]['vuln_rating'] is 'Critical': 840 | row_cells = vulnerable_table.add_row().cells 841 | row_cells[0].text = retest[i]['vuln_rating'] 842 | row_cells[1].text = retest[i]['vuln_title'] 843 | hosts = [] 844 | for h in retest[i]['v_hosts']: 845 | hosts.append(h[0]) 846 | row_cells[2].text = ((str(ip_sort_list(hosts)).replace("'", "")).lstrip("[")).rstrip("]") 847 | 848 | #Highs 849 | for i in retest: 850 | # "and retest[i]['vuln_rating'] is not 'Informational' and len(retest[i]['v_hosts']) > 0" Contriubted by Zach 851 | if 'v_hosts' in retest[i] and retest[i]['vuln_rating'] is not 'Informational' and len(retest[i]['v_hosts']) > 0: 852 | if retest[i]['vuln_rating'] is 'High': 853 | row_cells = vulnerable_table.add_row().cells 854 | row_cells[0].text = retest[i]['vuln_rating'] 855 | row_cells[1].text = retest[i]['vuln_title'] 856 | hosts = [] 857 | for h in retest[i]['v_hosts']: 858 | hosts.append(h[0]) 859 | row_cells[2].text = ((str(ip_sort_list(hosts)).replace("'", "")).lstrip("[")).rstrip("]") 860 | 861 | #Mediums 862 | for i in retest: 863 | # "and retest[i]['vuln_rating'] is not 'Informational' and len(retest[i]['v_hosts']) > 0" Contriubted by Zach 864 | if 'v_hosts' in retest[i] and retest[i]['vuln_rating'] is not 'Informational' and len(retest[i]['v_hosts']) > 0: 865 | if retest[i]['vuln_rating'] is 'Medium': 866 | row_cells = vulnerable_table.add_row().cells 867 | row_cells[0].text = retest[i]['vuln_rating'] 868 | row_cells[1].text = retest[i]['vuln_title'] 869 | hosts = [] 870 | for h in retest[i]['v_hosts']: 871 | hosts.append(h[0]) 872 | row_cells[2].text = ((str(ip_sort_list(hosts)).replace("'", "")).lstrip("[")).rstrip("]") 873 | 874 | #Lows 875 | for i in retest: 876 | # "and retest[i]['vuln_rating'] is not 'Informational' and len(retest[i]['v_hosts']) > 0" Contriubted by Zach 877 | if 'v_hosts' in retest[i] and retest[i]['vuln_rating'] is not 'Informational' and len(retest[i]['v_hosts']) > 0: 878 | if retest[i]['vuln_rating'] is 'Low': 879 | row_cells = vulnerable_table.add_row().cells 880 | row_cells[0].text = retest[i]['vuln_rating'] 881 | row_cells[1].text = retest[i]['vuln_title'] 882 | hosts = [] 883 | for h in retest[i]['v_hosts']: 884 | hosts.append(h[0]) 885 | row_cells[2].text = ((str(ip_sort_list(hosts)).replace("'", "")).lstrip("[")).rstrip("]") 886 | 887 | 888 | # Build stats table 889 | o_total_c = 0 # Original Total Critical 890 | r_total_c = 0 # Retest Total Critical 891 | o_total_h = 0 892 | r_total_h = 0 893 | o_total_m = 0 894 | r_total_m = 0 895 | o_total_l = 0 896 | r_total_l = 0 897 | for i in retest: 898 | # Critical Vulnerabilities 899 | if retest[i]['vuln_rating'] is 'Critical': 900 | o_total_c += retest[i]['total_orig'] 901 | if 'v_hosts' in retest[i]: 902 | r_total_c += len(retest[i]['v_hosts']) 903 | # High Vulnerabilities 904 | if retest[i]['vuln_rating'] is 'High': 905 | o_total_h += retest[i]['total_orig'] 906 | if 'v_hosts' in retest[i]: 907 | r_total_h += len(retest[i]['v_hosts']) 908 | # Medium Vulnerabilities 909 | if retest[i]['vuln_rating'] is 'Medium': 910 | o_total_m += retest[i]['total_orig'] 911 | if 'v_hosts' in retest[i]: 912 | r_total_m += len(retest[i]['v_hosts']) 913 | # Low Vulnerabilities 914 | if retest[i]['vuln_rating'] is 'Low': 915 | o_total_l += retest[i]['total_orig'] 916 | if 'v_hosts' in retest[i]: 917 | r_total_l += len(retest[i]['v_hosts']) 918 | 919 | s = "The original security assessment identified (" + str(o_total_c) + ") critical-severity, (" + str(o_total_h) + ") high-severity, (" + str(o_total_m) + ") medium-severity, and (" + str(o_total_l) + ") low-severity vulnerabilities." 920 | 921 | # Setup Table 922 | retest_report.add_heading('Retest Statistics') 923 | retest_report.add_paragraph(s) 924 | stats_table = retest_report.add_table(rows=1, cols=5) 925 | stats_table.style = 'Medium Grid 1 Accent 1' 926 | hdr_cells = stats_table.rows[0].cells 927 | hdr_cells[0].text = '' 928 | hdr_cells[1].text = 'Critical' 929 | hdr_cells[2].text = 'High' 930 | hdr_cells[3].text = 'Medium' 931 | hdr_cells[4].text = 'Low' 932 | # Original Assessment Numbers 933 | row_cells = stats_table.add_row().cells 934 | row_cells[0].text = 'Original' 935 | row_cells[1].text = str(o_total_c) 936 | row_cells[2].text = str(o_total_h) 937 | row_cells[3].text = str(o_total_m) 938 | row_cells[4].text = str(o_total_l) 939 | 940 | # Retest Assessment Numbers 941 | row_cells = stats_table.add_row().cells 942 | row_cells[0].text = 'Retest' 943 | row_cells[1].text = str(r_total_c) 944 | row_cells[2].text = str(r_total_h) 945 | row_cells[3].text = str(r_total_m) 946 | row_cells[4].text = str(r_total_l) 947 | 948 | save_report(retest_report, retest_assessment) 949 | 950 | 951 | def main_menu(): 952 | """Display the main menu""" 953 | 954 | logging.info('Entered into main_menu function') 955 | i = None 956 | valid_options = {1: generate_assessment_report, 957 | 2: sql_dump, 958 | 3: retest, 959 | 4: patch_gauntlet, 960 | 5: pentest_checklist, 961 | 6: generate_assessment_json, 962 | 7: exit, 963 | } 964 | os.system('clear') 965 | banner() 966 | try: 967 | while i is None: 968 | print "\t\t\t\033[0;0;37mGUINEVERE MAIN MENU\033[0m\n" 969 | print "[1]Generate Assessment Report" 970 | print "[2]Export Assessment" 971 | print "[3]Generate Retest Report" 972 | print "[4]Patch Gauntled Database" 973 | print "[5]Generate Pentest Checklist" 974 | print "[6]Generate Assessment JSON File" 975 | print "[7]Exit" 976 | i = raw_input("\nWhat would you like to do: ") 977 | if int(i) in valid_options: 978 | valid_options[int(i)]() 979 | else: 980 | os.system('clear') 981 | banner() 982 | print "["+warn+"]" + str(i) + " is not a valid option, please try again: " 983 | i = None 984 | except ValueError: 985 | main_menu() 986 | 987 | 988 | def sql_dump(): 989 | """Use mysqldump to export an assessment to a .sql file""" 990 | 991 | import subprocess, time, platform 992 | 993 | #Find the script location and set the mysqldump executable 994 | #Will implement functionality in the future 995 | script_path = os.path.dirname(os.path.realpath(__file__)) 996 | bin_path = os.path.join(script_path, 'bin') 997 | #Determine the operating system 998 | operating_system = platform.platform() 999 | if 'Windows' in operating_system: 1000 | ext = '.exe' 1001 | elif 'Linux' in operating_system: 1002 | ext = '.bin' 1003 | else: 1004 | ext = '.nothing' 1005 | mysqldump = "mysqldump" 1006 | if os.path.isdir(bin_path) and os.path.exists(bin_path+'mysqldump.'+ext): 1007 | mysqldump = bin_path+'mysqldump.'+ext 1008 | 1009 | assessment = get_assessment("the assessment to backup") 1010 | os.system('clear') 1011 | banner() 1012 | output_path = get_path() 1013 | date_time = time.strftime('%m%d%Y-%H%M%S') 1014 | try: 1015 | sql_file = open(os.path.join(output_path, assessment+"_"+date_time+".sql"), "w") 1016 | subprocess.call([mysqldump, "--host="+args.db_host, "-u", args.db_user, "-p"+args.db_pass, "gauntlet_"+assessment], stdout=sql_file) 1017 | #os.system(mysqldump+" --host="+args.db_host+" -u "+args.db_user+" -p"+args.db_pass+" gauntlet_"+assessment+" > "+sql_file) 1018 | print "["+warn+"]SQL file saved to: " + os.path.join(output_path, assessment+"_"+date_time+".sql") 1019 | except OSError: 1020 | print "["+warn+"]mysqldump is likely not in your path, please add it and try again" 1021 | raise 1022 | except: 1023 | raise #Just use for debug 1024 | raw_input("["+question+"]Press enter to continue...") 1025 | main_menu() 1026 | 1027 | 1028 | def get_path(): 1029 | """Prompt the user to enter a directory path""" 1030 | 1031 | output_path = None 1032 | while output_path is None: 1033 | print "["+question+"]Please enter the directory where you would like the file saved?" 1034 | output_path = raw_input() 1035 | if os.path.isdir(os.path.expanduser(output_path)): 1036 | pass 1037 | else: 1038 | os.system('clear') 1039 | banner() 1040 | print "["+warn+"]" + str(output_path) + " is not valid, please try again: " 1041 | output_path = None 1042 | return os.path.expanduser(output_path) 1043 | 1044 | 1045 | def write_single_vul(rpt, report): 1046 | """Write the single vulnerability paragraph""" 1047 | 1048 | cvss = None 1049 | if len(rpt['vulns']) is 1: 1050 | for r in rpt['vulns']: 1051 | cvss = get_cvss(r) 1052 | 1053 | if args.cvss: 1054 | report.add_heading("%s (CVSS: %0.1f - %s)" % (rpt['report_title'], cvss.base_score, rpt['report_rating']), 1055 | level=4) 1056 | else: 1057 | report.add_heading("%s (%s)" % (rpt['report_title'], rpt['report_rating']), level=4) 1058 | 1059 | for i in rpt['vulns']: 1060 | vulnerableHosts = [] 1061 | for j in set(rpt['vulns'][i]['vuln_hosts']): # Build Single Dimensional List 1062 | vulnerableHosts.append(j) 1063 | sortedHosts = ip_sort(vulnerableHosts) # Create a unique & sorted list of hosts (avoids duplicate hosts) 1064 | hosts = [] 1065 | for h in sortedHosts: 1066 | for h2 in vulnerableHosts: 1067 | if h2[0] == h: 1068 | if args.ports: 1069 | if len(h2) == 3: 1070 | if h2[1] != '0' and h2[2] != 'icmp': 1071 | host_info = h2[0] + ":" + h2[1] + "/" + h2[2] 1072 | if host_info not in hosts: 1073 | hosts.append(host_info) 1074 | else: 1075 | if h2[0] not in hosts: 1076 | hosts.append(h2[0]) 1077 | else: 1078 | if h2[0] not in hosts: 1079 | hosts.append(h2[0]) 1080 | else: 1081 | if h2[0] not in hosts: 1082 | hosts.append(h2[0]) 1083 | 1084 | if len(hosts) == 1: # If there is just one host 1085 | p = rpt['report_identification'].replace("[n]", int_to_string(len(hosts))+ ' ('+hosts[0]+')') 1086 | elif len(hosts) == 2: # If there are two hosts 1087 | p = rpt['report_identification'].replace("[n]", int_to_string(len(hosts))+ ' ('+hosts[0]+' and '+hosts[1]+')') 1088 | elif len(hosts) >= 2 and len(hosts) <=5: # If there are more than two but lest than five hosts 1089 | host_list = "" 1090 | for h in hosts: 1091 | if h is hosts[len(hosts)-1]: # Check to see if this is the last item in the list 1092 | host_list += "and " + h + ") " 1093 | else: 1094 | host_list += h + ", " 1095 | p = rpt['report_identification'].replace("[n]", int_to_string(len(hosts)) + ' ('+host_list) 1096 | elif len(hosts) >= 6: # If there are six or more hosts 1097 | p = rpt['report_identification'].replace("[n]", int_to_string(len(hosts)) + '(refer to TABLE X)') 1098 | 1099 | if p.endswith(" ") or rpt['report_explanation'].startswith(" "): 1100 | p += rpt['report_explanation'] 1101 | else: 1102 | p += (" " + rpt['report_explanation']) 1103 | if p.endswith(" ") or rpt['report_impact'].startswith(" "): 1104 | p += rpt['report_impact'] 1105 | else: 1106 | p += (" " + rpt['report_impact']) 1107 | p = report.add_paragraph(p, style='Normal') 1108 | p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY 1109 | p = report.add_paragraph(rpt['report_recommendation'], style='Normal') 1110 | p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY 1111 | 1112 | if args.debug: 1113 | print debug + "Vulnerable Hosts: %s" % hosts 1114 | raw_input(debug + "Press any key to continue") 1115 | if len(hosts) >= 6: # Draw the table 1116 | c = 4 # number of desired columns 1117 | r = int(math.ceil((len(hosts) / float(4)))) # Determine number of rows for table using a max of 4 columns 1118 | hosts_table = report.add_table(rows=r, cols=c) 1119 | hosts_table.style = 'Medium Grid 1 Accent 1' 1120 | z = 0 # number of hosts 1121 | x = 0 # row indices 1122 | y = 0 # column indices 1123 | while z < len(hosts): 1124 | if (y / float(c)) == 1: # Determine if we need to start putting data on a new row 1125 | y = 0 # reset column indices since max number of columns reached 1126 | x += 1 1127 | hosts_table.cell(x, y).text = hosts[z] 1128 | z += 1 1129 | y += 1 # Add one to up the column data is put in 1130 | if len(hosts)/float(c) != 1.000: # Add "---" for empty spots in table 1131 | d = c * (x+1) 1132 | while d > len(hosts): 1133 | hosts_table.cell(x, y).text = "---" 1134 | d -= 1 1135 | y += 1 1136 | 1137 | return report 1138 | 1139 | 1140 | def write_multi_vul(rpt, report): 1141 | """Write report data for grouped or multi vulnerabilities""" 1142 | 1143 | total_hosts = 0 1144 | for i in rpt['vulns']: 1145 | if args.sC and rpt['vulns'][i]['vuln_rating'] == 'Critical': 1146 | total_hosts += len(rpt['vulns'][i]['vuln_hosts']) 1147 | elif args.sH and rpt['vulns'][i]['vuln_rating'] == 'High': 1148 | total_hosts += len(rpt['vulns'][i]['vuln_hosts']) 1149 | elif args.sM and rpt['vulns'][i]['vuln_rating'] == 'Medium': 1150 | total_hosts += len(rpt['vulns'][i]['vuln_hosts']) 1151 | elif args.sL and rpt['vulns'][i]['vuln_rating'] == 'Low': 1152 | total_hosts += len(rpt['vulns'][i]['vuln_hosts']) 1153 | elif args.sI and rpt['vulns'][i]['vuln_rating'] == 'Informational': 1154 | total_hosts += len(rpt['vulns'][i]['vuln_hosts']) 1155 | 1156 | report.add_heading(rpt['report_title'] + " (" + rpt['report_rating']+")", level=4) 1157 | p = rpt['report_identification'].replace("[n]", int_to_string(total_hosts)) 1158 | p = p.rstrip('\n') 1159 | p = p.rstrip('\t') 1160 | p = p.rstrip() 1161 | 1162 | if p.endswith(" ") or rpt['report_explanation'].startswith(" "): 1163 | p += rpt['report_explanation'] 1164 | else: 1165 | p += (" " + rpt['report_explanation']) 1166 | if p.endswith(" ") or rpt['report_impact'].startswith(" "): 1167 | p += rpt['report_impact'] 1168 | else: 1169 | p += (" " + rpt['report_impact']) 1170 | 1171 | p = report.add_paragraph(p, style='Normal') 1172 | p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY 1173 | p = report.add_paragraph(rpt['report_recommendation'], style='Normal') 1174 | p.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY 1175 | 1176 | table = None 1177 | hdr_cells = None 1178 | 1179 | if args.cvss: 1180 | table = report.add_table(rows=1, cols=4) 1181 | hdr_cells = table.rows[0].cells 1182 | hdr_cells[0].text = 'Severity' 1183 | hdr_cells[1].text = 'CVSS' 1184 | hdr_cells[2].text = 'Vulnerability' 1185 | hdr_cells[3].text = 'Affected Host(s)' 1186 | else: 1187 | table = report.add_table(rows=1, cols=3) 1188 | hdr_cells = table.rows[0].cells 1189 | hdr_cells[0].text = 'Severity' 1190 | hdr_cells[1].text = 'Vulnerability' 1191 | hdr_cells[2].text = 'Affected Host(s)' 1192 | 1193 | table.style = 'Medium Grid 1 Accent 1' 1194 | 1195 | def writeRow(r, t, h, c=None): 1196 | row_cells = table.add_row().cells 1197 | if c is not None: 1198 | row_cells[0].text = r 1199 | row_cells[1].text = c 1200 | row_cells[2].text = t 1201 | row_cells[3].text = h 1202 | else: 1203 | row_cells[0].text = r 1204 | row_cells[1].text = t 1205 | row_cells[2].text = h 1206 | 1207 | for i in rpt['vulns']: 1208 | if args.sC and rpt['vulns'][i]['vuln_rating'] == 'Critical': 1209 | cvss = get_cvss(i) 1210 | if args.cvss: 1211 | writeRow(rpt['vulns'][i]['vuln_rating'], rpt['vulns'][i]['vuln_title'], 1212 | str(len(rpt['vulns'][i]['vuln_hosts'])), "%0.1f" % cvss.base_score) 1213 | else: 1214 | writeRow(rpt['vulns'][i]['vuln_rating'], rpt['vulns'][i]['vuln_title'], 1215 | str(len(rpt['vulns'][i]['vuln_hosts']))) 1216 | for i in rpt['vulns']: 1217 | if args.sH and rpt['vulns'][i]['vuln_rating'] == 'High': 1218 | cvss = get_cvss(i) 1219 | if args.cvss: 1220 | writeRow(rpt['vulns'][i]['vuln_rating'], rpt['vulns'][i]['vuln_title'], 1221 | str(len(rpt['vulns'][i]['vuln_hosts'])), "%0.1f" % cvss.base_score) 1222 | else: 1223 | writeRow(rpt['vulns'][i]['vuln_rating'], rpt['vulns'][i]['vuln_title'], 1224 | str(len(rpt['vulns'][i]['vuln_hosts']))) 1225 | for i in rpt['vulns']: 1226 | if args.sM and rpt['vulns'][i]['vuln_rating'] == 'Medium': 1227 | cvss = get_cvss(i) 1228 | if args.cvss: 1229 | writeRow(rpt['vulns'][i]['vuln_rating'], rpt['vulns'][i]['vuln_title'], 1230 | str(len(rpt['vulns'][i]['vuln_hosts'])), "%0.1f" % cvss.base_score) 1231 | else: 1232 | writeRow(rpt['vulns'][i]['vuln_rating'], rpt['vulns'][i]['vuln_title'], 1233 | str(len(rpt['vulns'][i]['vuln_hosts']))) 1234 | for i in rpt['vulns']: 1235 | if args.sL and rpt['vulns'][i]['vuln_rating'] == 'Low': 1236 | cvss = get_cvss(i) 1237 | if args.cvss: 1238 | writeRow(rpt['vulns'][i]['vuln_rating'], rpt['vulns'][i]['vuln_title'], 1239 | str(len(rpt['vulns'][i]['vuln_hosts'])), "%0.1f" % cvss.base_score) 1240 | else: 1241 | writeRow(rpt['vulns'][i]['vuln_rating'], rpt['vulns'][i]['vuln_title'], 1242 | str(len(rpt['vulns'][i]['vuln_hosts']))) 1243 | for i in rpt['vulns']: 1244 | if args.sI and rpt['vulns'][i]['vuln_rating'] == 'Informational': 1245 | cvss = get_cvss(i) 1246 | if args.cvss: 1247 | writeRow(rpt['vulns'][i]['vuln_rating'], rpt['vulns'][i]['vuln_title'], 1248 | str(len(rpt['vulns'][i]['vuln_hosts'])), "%0.1f" % cvss.base_score) 1249 | else: 1250 | writeRow(rpt['vulns'][i]['vuln_rating'], rpt['vulns'][i]['vuln_title'], 1251 | str(len(rpt['vulns'][i]['vuln_hosts']))) 1252 | 1253 | return report 1254 | 1255 | 1256 | def write_all_vuln(vuln, the_Report): 1257 | 1258 | print "["+note+"]Writing list of all vulnerabilities to the report: " 1259 | the_Report.add_page_break() 1260 | the_Report.add_heading("List of Assessment Vulnerabilities", 1) 1261 | for i in vuln: 1262 | if args.sC and vuln[i]['vuln_rating'] is 'Critical': 1263 | print "\t["+info+"]"+ vuln[i]['vuln_title'], "(" + vuln[i]['vuln_rating'] + ")" 1264 | the_Report.add_heading(vuln[i]['vuln_title'] + " (" + vuln[i]['vuln_rating'] + ")", 3) 1265 | if args.all_verb: 1266 | the_Report.add_paragraph(vuln[i]['vuln_desc'], style='Body Text') 1267 | the_Report.add_paragraph(vuln[i]['vuln_sol'], style='Body Text') 1268 | for i in vuln: 1269 | if args.sH and vuln[i]['vuln_rating'] is 'High': 1270 | print "\t["+info+"]"+ vuln[i]['vuln_title'], "(" + vuln[i]['vuln_rating'] + ")" 1271 | the_Report.add_heading(vuln[i]['vuln_title'] + " (" + vuln[i]['vuln_rating'] + ")", 3) 1272 | if args.all_verb: 1273 | the_Report.add_paragraph(vuln[i]['vuln_desc'], style='Body Text') 1274 | the_Report.add_paragraph(vuln[i]['vuln_sol'], style='Body Text') 1275 | for i in vuln: 1276 | if args.sM and vuln[i]['vuln_rating'] is 'Medium': 1277 | print "\t["+info+"]"+ vuln[i]['vuln_title'], "(" + vuln[i]['vuln_rating'] + ")" 1278 | the_Report.add_heading(vuln[i]['vuln_title'] + " (" + vuln[i]['vuln_rating'] + ")", 3) 1279 | if args.all_verb: 1280 | the_Report.add_paragraph(vuln[i]['vuln_desc'], style='Body Text') 1281 | the_Report.add_paragraph(vuln[i]['vuln_sol'], style='Body Text') 1282 | for i in vuln: 1283 | if args.sL and vuln[i]['vuln_rating'] is 'Low': 1284 | print "\t["+info+"]"+ vuln[i]['vuln_title'], "(" + vuln[i]['vuln_rating'] + ")" 1285 | the_Report.add_heading(vuln[i]['vuln_title'] + " (" + vuln[i]['vuln_rating'] + ")", 3) 1286 | if args.all_verb: 1287 | the_Report.add_paragraph(vuln[i]['vuln_desc'], style='Body Text') 1288 | the_Report.add_paragraph(vuln[i]['vuln_sol'], style='Body Text') 1289 | for i in vuln: 1290 | if args.sI and vuln[i]['vuln_rating'] is 'Informational': 1291 | print "\t["+info+"]"+ vuln[i]['vuln_title'], "(" + vuln[i]['vuln_rating'] + ")" 1292 | the_Report.add_heading(vuln[i]['vuln_title'] + " (" + vuln[i]['vuln_rating'] + ")", 3) 1293 | if args.all_verb: 1294 | the_Report.add_paragraph(vuln[i]['vuln_desc'], style='Body Text') 1295 | the_Report.add_paragraph(vuln[i]['vuln_sol'], style='Body Text') 1296 | #if vuln[i]['vuln_report_id'] is None and (((vuln[i]['vuln_rating'] is "Critical") and args.sC) or ((vuln[i]['vuln_rating'] is "High") and args.sH) or ((vuln[i]['vuln_rating'] is "Medium") and args.sM) or ((vuln[i]['vuln_rating'] is "Low") and args.sL) or ((vuln[i]['vuln_rating'] is "Informational") and args.sI)): 1297 | #print "\t["+info+"]"+ vuln[i]['vuln_title'], "(" + vuln[i]['vuln_rating'] + ")" 1298 | #the_Report.add_heading(vuln[i]['vuln_title'] + " (" + vuln[i]['vuln_rating'] + ")", 3) 1299 | #if args.all_verb: 1300 | #the_Report.add_paragraph(vuln[i]['vuln_desc'], style='BodyText') 1301 | #the_Report.add_paragraph(vuln[i]['vuln_sol'], style='BodyText') 1302 | 1303 | return the_Report 1304 | 1305 | 1306 | def generate_assessment_report(): 1307 | """The main function for automatically generating an assessment report""" 1308 | 1309 | logging.info('Entering the generate_assessment_report function') 1310 | os.system('clear') 1311 | banner() 1312 | print "Retrieving available assessments..." 1313 | 1314 | # Database containing the options of crosstables 1315 | assessment = get_assessment("the assessment to create a report for") 1316 | banner() 1317 | 1318 | # Choose crosstable from the options listed from get_assessment() 1319 | crosstable = get_crosstable(assessment) 1320 | 1321 | vulnerability_ID_list = assessment_vulns(assessment, crosstable) 1322 | os.system('clear') 1323 | banner() 1324 | print "["+note+"]Building list of found vulnerabilities for " + assessment + " Crosstable " + crosstable + "..." 1325 | vuln_ID_to_info_mapping = get_vulns(vulnerability_ID_list, assessment, crosstable) 1326 | 1327 | ## FOLLOW vuln_id_to_info_mapping - Contains the severity rating, needs to be reset after each crosstable 1328 | 1329 | print "["+note+"]Generating report for the following vulnerabilities:" 1330 | 1331 | # Unique identifier for reporting record, allows access to verbage 1332 | report_record_ID_set = assessment_report(vuln_ID_to_info_mapping) 1333 | 1334 | # Returns huge dictionary of vulnerabilities and associated data 1335 | assessment_db = get_report(report_record_ID_set, vuln_ID_to_info_mapping) 1336 | 1337 | # Make the word document 1338 | the_Report = docx.Document() 1339 | the_Report.add_heading(assessment, 1) 1340 | the_Report = generate_vuln_list(the_Report, assessment, assessment_db) 1341 | 1342 | if ((len(assessment_db) is 0) and args.all_vulns is False): 1343 | exit("["+warn+"]Nothing to report on, quitting...") 1344 | 1345 | #################################### 1346 | # Write the report in severity order 1347 | #################################### 1348 | 1349 | logging.info("Writing the critical vulnerability narratives to the report") 1350 | 1351 | for i in assessment_db: 1352 | if assessment_db[i]['report_rating'] == 'Critical' and args.sC: 1353 | 1354 | # Grouped Vulnerability Write-up 1355 | if len(assessment_db[i]['vulns']) > 1: 1356 | print '\t['+info+']Multi finding: ', assessment_db[i]['report_title'] 1357 | logging.info('['+info+']Multi finding: ' + assessment_db[i]['report_title']) 1358 | the_report = write_multi_vul(assessment_db[i], the_Report) 1359 | 1360 | # Single Vulnerability Write-up 1361 | elif assessment_db[i]['report_rating'] is not None: 1362 | logging.info("["+info+"]" + assessment_db[i]['report_title'] + 1363 | "(" + assessment_db[i]['report_rating'] + ")") 1364 | print "\t["+info+"]" + assessment_db[i]['report_title'] + " \033[0;0;31m(" + \ 1365 | assessment_db[i]['report_rating'] + ")\033[0m" 1366 | the_Report = write_single_vul(assessment_db[i], the_Report) 1367 | 1368 | logging.info("Writing the high vulnerability narratives to the report") 1369 | 1370 | for i in assessment_db: 1371 | if assessment_db[i]['report_rating'] == 'High' and args.sH: 1372 | if len(assessment_db[i]['vulns']) > 1: 1373 | logging.info('['+info+']Multi finding: ' + assessment_db[i]['report_title']) 1374 | print '\t['+info+']Multi finding: ', assessment_db[i]['report_title'] 1375 | the_report = write_multi_vul(assessment_db[i], the_Report) 1376 | elif assessment_db[i]['report_rating'] is not None: 1377 | logging.info("["+info+"]" + assessment_db[i]['report_title'] + 1378 | "(" + assessment_db[i]['report_rating'] + ")") 1379 | print "\t["+info+"]" + assessment_db[i]['report_title'] + " \033[0;0;35m(" + \ 1380 | assessment_db[i]['report_rating'] + ")\033[0m" 1381 | the_Report = write_single_vul(assessment_db[i], the_Report) 1382 | 1383 | logging.info("Writing the medium vulnerability narratives to the report") 1384 | 1385 | for i in assessment_db: 1386 | if assessment_db[i]['report_rating'] == 'Medium' and args.sM: 1387 | if len(assessment_db[i]['vulns']) > 1: 1388 | logging.info('['+info+']Multi finding: ' + assessment_db[i]['report_title']) 1389 | print '\t['+info+']Multi finding: ', assessment_db[i]['report_title'] 1390 | the_report = write_multi_vul(assessment_db[i], the_Report) 1391 | elif assessment_db[i]['report_rating'] is not None: 1392 | logging.info("["+info+"]" + assessment_db[i]['report_title'] + 1393 | "(" + assessment_db[i]['report_rating'] + ")") 1394 | print "\t["+info+"]" + assessment_db[i]['report_title'] + " \033[0;0;33m(" + \ 1395 | assessment_db[i]['report_rating'] + ")\033[0m" 1396 | the_Report = write_single_vul(assessment_db[i], the_Report) 1397 | 1398 | logging.info("Writing the low vulnerability narratives to the report") 1399 | 1400 | for i in assessment_db: 1401 | if assessment_db[i]['report_rating'] == 'Low' and args.sL: 1402 | if len(assessment_db[i]['vulns']) > 1: 1403 | logging.info('['+info+']Multi finding: ' + assessment_db[i]['report_title']) 1404 | print '\t['+info+']Multi finding: ', assessment_db[i]['report_title'] 1405 | the_report = write_multi_vul(assessment_db[i], the_Report) 1406 | elif assessment_db[i]['report_rating'] is not None: 1407 | logging.info("["+info+"]" + assessment_db[i]['report_title'] + 1408 | "(" + assessment_db[i]['report_rating'] + ")") 1409 | print "\t["+info+"]" + assessment_db[i]['report_title'] + " \033[0;0;34m(" + \ 1410 | assessment_db[i]['report_rating'] + ")\033[0m" 1411 | the_Report = write_single_vul(assessment_db[i], the_Report) 1412 | 1413 | logging.info("Writing the informational vulnerability narratives to the report") 1414 | 1415 | for i in assessment_db: 1416 | if assessment_db[i]['report_rating'] == 'Informational' and args.sI: 1417 | if len(assessment_db[i]['vulns']) > 1: 1418 | logging.info('['+info+']Multi finding: ' + assessment_db[i]['report_title']) 1419 | print '\t['+info+']Multi finding: ', assessment_db[i]['report_title'] 1420 | the_report = write_multi_vul(assessment_db[i], the_Report) 1421 | elif assessment_db[i]['report_rating'] is not None: 1422 | logging.info("["+info+"]" + assessment_db[i]['report_title'] + 1423 | "(" + assessment_db[i]['report_rating'] + ")") 1424 | print "\t["+info+"]" + assessment_db[i]['report_title'] + " \033[0;0;37m(" + \ 1425 | assessment_db[i]['report_rating'] + ")\033[0m" 1426 | the_Report = write_single_vul(assessment_db[i], the_Report) 1427 | 1428 | if args.all_vulns: 1429 | the_Report = write_all_vuln(vuln_ID_to_info_mapping, the_Report) 1430 | the_Report = generate_hosts_table(the_Report, assessment) 1431 | save_report(the_Report, assessment) 1432 | 1433 | 1434 | def generate_assessment_json(): 1435 | """Generate a JSON object of the aggregated assessment information""" 1436 | 1437 | logging.info('Entering the generate_assessment_json function') 1438 | os.system('clear') 1439 | banner() 1440 | print "Retrieving available assessments..." 1441 | assessment = get_assessment("the assessment to create a JSON object for") 1442 | banner() 1443 | crosstable = get_crosstable(assessment) 1444 | vID = assessment_vulns(assessment, crosstable) 1445 | os.system('clear') 1446 | banner() 1447 | print "["+note+"]Building JSON object for " + assessment + " Crosstable " + crosstable + "..." 1448 | vuln = get_vulns(vID, assessment, crosstable) 1449 | rID = assessment_report(vuln) 1450 | assessment_db = get_report(rID, vuln) 1451 | engagment_details = gather_assessment_details(assessment) 1452 | json_dict = {'engagment_details': engagment_details, 'report': assessment_db} 1453 | json_object = json.dumps(json_dict) 1454 | out_dir = get_path() 1455 | json_file = os.path.join(out_dir, "Guinevere_" + assessment + "_" + crosstable + ".json") 1456 | with open(json_file, "w") as j: 1457 | j.write(json_object) 1458 | print "["+warn+"]Assessment JSON object saved to: " + json_file 1459 | raw_input("["+question+"]Press enter to continue...") 1460 | main_menu() 1461 | 1462 | 1463 | def gather_assessment_details(assessment): 1464 | """Gather assessment details from Gauntlet and create a dictionary""" 1465 | 1466 | logging.info('Entering the gather_assessment_details function') 1467 | engagement = db_query("""SELECT value FROM gauntlet_%s.engagement_details WHERE 1468 | engagement_details.key = 'Engagement Task 1'""" % (assessment), assessment)[0][0] 1469 | start_date = db_query("""SELECT value FROM gauntlet_%s.engagement_details WHERE 1470 | engagement_details.key = 'Start Date'""" % (assessment), assessment)[0][0] 1471 | end_date = db_query("""SELECT value FROM gauntlet_%s.engagement_details WHERE 1472 | engagement_details.key = 'End Date'""" % (assessment), assessment)[0][0] 1473 | analyst = db_query("""SELECT value FROM gauntlet_%s.engagement_details WHERE 1474 | engagement_details.key = 'Analyst 1'""" % (assessment), assessment)[0][0] 1475 | 1476 | i = {'engagment_type': engagement, 'start_date': start_date, 'stop_date': end_date, 'analyst': analyst} 1477 | return i 1478 | 1479 | 1480 | def patch_gauntlet(): 1481 | print "Nothing to test right now" 1482 | db = MySQLdb.connect(host=args.db_host, user=args.db_user, passwd=args.db_pass, port=args.db_port, db='GauntletData') 1483 | 1484 | create_table = """ 1485 | CREATE TABLE report ( 1486 | report_id integer NOT NULL AUTO_INCREMENT, 1487 | title character varying(255) NOT NULL DEFAULT '', 1488 | identification blob, 1489 | explanation blob, 1490 | impact blob, 1491 | recommendation blob, 1492 | status ENUM('NEW','MODIFIED','ACCEPTED','MARKED','DELETED') NOT NULL, 1493 | owner character varying(255) NOT NULL DEFAULT '', 1494 | PRIMARY KEY (report_id) 1495 | );""" 1496 | mod_report = """ALTER TABLE report AUTO_INCREMENT = 50000;""" 1497 | mod_vuln_1 = """ALTER TABLE vulns ADD report_id int;""" 1498 | mod_vuln_2 = """ALTER TABLE vulns ADD FOREIGN KEY (report_id) REFERENCES report(report_id);""" 1499 | 1500 | os.system('clear') 1501 | banner() 1502 | print """["""+warn+"""]Please make sure you have previously selected "(Re-)Initialize Server" in Gauntlet.""" 1503 | raw_input("["+question+"]Press enter to continue...") 1504 | try: 1505 | gauntlet = db.cursor() 1506 | gauntlet.execute(create_table) 1507 | gauntlet.execute(mod_report) 1508 | gauntlet.execute(mod_vuln_1) 1509 | gauntlet.execute(mod_vuln_2) 1510 | gauntlet.close() 1511 | except: 1512 | print "\n["+warn+"]Please report this error to " + __maintainer__ + " by email at: " + __email__ 1513 | raise 1514 | main_menu() 1515 | 1516 | print "["+note+"]You can now upload a new master dataset to Gauntlet" 1517 | main_menu() 1518 | 1519 | 1520 | def pentest_checklist(): 1521 | """Generate a pentest checklist to be used for an assessment""" 1522 | 1523 | logging.info('Entered into pentest_checklist function') 1524 | 1525 | def build_html(): 1526 | """Generate HTML pentest checklist""" 1527 | 1528 | html_part = '' 1529 | 1530 | host_part = '\n
\n\t\t | $host-name | \n\t
---|---|
\n\t\t | $port-text | \n\t
$vuln-title | \n\t|
' \
1537 | '$tool-output | \n\t|
\n\t |