class VulnReportReader: def __init__(self): self.console = ConsoleHandler("VulnReader") # read the vuln report from a CSV file and return a list of Vulnerability objects def read_csv(self, path): self.console.info("Start reading csv file: {}".format(path)) entries = [] try: with open(path) as csv_file: csv_reader = csv.DictReader(csv_file, delimiter=",") line_count = 0 for row in csv_reader: port = -1 cvss = -1.0 try: try: port = int(row['Port']) except ValueError: self.console.warn( "\t Cannot convert 'Port' value from vuln report at line {}" .format(line_count)) try: cvss = float(row['CVSS']) except ValueError: self.console.warn( "\t cannot convert 'CVSS' value from vuln report at line {}" .format(line_count)) vuln = Vulnerability( row['IP'], port=port, cvss=cvss, protocol=row['Protocol'], cve=row['CVEs'], bid=row['BIDs'], name=row['Name'], detection=row['Detection Method'], solution_type=row['Solution Type']) entries.append(vuln) line_count += 1 except KeyError as e: self.console.error( "\t Cannot read column from report file: {}". format(e)) raise KeyError self.console.info( "Read {} entries from Vulnerability Scanner Output".format( line_count)) return entries except (TypeError, FileNotFoundError): self.console.error("Cannot read report file. Aborting...") raise FileNotFoundError
class ExplClassificationReader: def __init__(self, db_handler): self.console = ConsoleHandler("ExplClassR.") self.db = db_handler def write_to_db(self, path): self.console.info("Start reading csv file: {}".format(path)) try: with open(path) as csv_file: csv_reader = csv.DictReader(csv_file, delimiter=",") line_count = 0 for row in csv_reader: expl_class = ExploitClassification(row['Exploit'], row['Classification']) self.db.add_expl_class(expl_class) line_count += 1 self.console.info( "Written {} entries from Exploit Classification file to DB." .format(line_count - 1)) except (TypeError, FileNotFoundError): self.console.error( "Cannot open export classification file at {}.".format(path))
class CVEDetailParser: def __init__(self, db_handler): self.console = ConsoleHandler("CVEDetParser") self.db = db_handler def get_cve_details(self, cves): if cves == "NOCVE": return None cves = CVEDetailParser.parse_cves(self.console, cves) vuln_types = [] for cve in cves: cve_from_db = self.db.get_cve(cve) if cve_from_db is None: html = self.retrieve_cve_details(cve) vuln_type = self.parse_cve_type(html) if vuln_type is not None: self.console.debug( "\t successfully extracted vulnerability type for {}: {}" .format(cve, vuln_type)) vuln_types.append(vuln_type) self.db.add_cve_details({'cve': cve, 'type': vuln_type}) else: self.console.error( "\t Unable to extract Vulnerability type for {}". format(cve)) else: vuln_types.append(cve_from_db['type']) self.console.debug("received CVE type from DB: {} {}".format( cve_from_db['cve'], cve_from_db['type'])) if len(vuln_types) == 0: return None vuln_type = list(set(vuln_types)) self.console.debug("\t extracted vuln type: {}".format(vuln_type)) return vuln_type def retrieve_cve_details(self, cve): path = cve_details_config.get("url") + cve try: r = requests.get(path, timeout=20) if r.status_code != requests.codes.ok: self.console.error("\t unable to retrieve CVE details.") return r.text except requests.exceptions.ConnectionError: self.console.error( "Cannot retrieve CVE details. Please check your internet connection." ) return "" def parse_cve_type(self, html): match = re.search(CVE_TYPE_PARSER_PATTERN, html) if match is None: return None return match.group(CVE_TYPE_GROUP_ID_IN_PATTERN) # parses single CVE-ID from a string with several CVE-IDs separated by a comma @staticmethod def parse_cves(console, cve): parsed_cves = [] cves = cve.split(",") for i, _ in enumerate(cves): cves[i] = cves[i].strip() for cve in cves: match = re.match(CVE_PATTERN, cve) if match is not None: parsed_cves.append(cve) remove_count = len(cves) - len(parsed_cves) if remove_count > 0: console.debug( "removed {} CVE entries because of a non-matching format". format(remove_count)) return parsed_cves # parse a list of BIDs @staticmethod def parse_bids(bid_str): parsed_bids = [] bids = bid_str.split(",") for bid in bids: bid = bid.strip() parsed_bids.append(bid) return parsed_bids
class SessionHandler: def __init__(self, msf_handler): self.console = ConsoleHandler('Sess.Handler') self.msf_handler = msf_handler # checks if a session has spawned by executing the given exploits. # return it's ID if possible, -1 otherwise def session_check(self, exploit_path, rport): exploit_path = "exploit/{}".format(exploit_path) self.console.info("Start Session checker for {}".format(exploit_path)) sessions = self.msf_handler.get_all_sessions() if len(sessions) > 0: self.console.debug("\t session(s) discovered: {}".format(sessions)) for session_id in sessions: if sessions[session_id][ 'via_exploit'] == exploit_path and sessions[ session_id]['session_port'] == rport: self.console.info( "\t Got session {} via exploit {}".format( session_id, exploit_path)) return int(session_id) self.console.warn("\t apparently no session spawned successfully.") return NO_SESSION_FOUND # gather useful information for a given remote session, based on the type of session and close session afterwards def gather_info(self, session_id, os): os = OS.from_string(os) session_id = str(session_id) session_obj = self.msf_handler.get_session_obj(session_id=session_id) shell_info = { 'info': self.msf_handler.get_session_dict(session_id).get('info') } if self.msf_handler.get_session_dict( session_id=session_id).get('type') == 'meterpreter': self.console.debug('\t discovered a meterpreter shell') shell_info = self.run_default_meterpreter_cmds( session=session_obj, os=os, shell_info=shell_info) else: self.console.debug('\t discovered a normal session') shell_info = self.run_default_shell_cmds(shell=session_obj, os=os, shell_info=shell_info) self.exit_shell(shell=session_obj) self.close_single_session(session_id) self.console.debug("shell info: {}".format(shell_info)) return shell_info # run special commands if a meterpreter session is present def run_default_meterpreter_cmds(self, session, os, shell_info): shell_info['meterpreter: getuid'] = self.run_single_shell_cmd( session, 'getuid') shell_info['meterpreter: route'] = self.run_single_shell_cmd( session, 'route') self.downgrade_meterpreter(session) shell_info = self.run_default_shell_cmds(session, os, shell_info=shell_info) self.exit_shell(session) return shell_info def run_default_shell_cmds(self, shell, os, shell_info): shell_info['whoami'] = self.run_single_shell_cmd(shell, 'whoami') if os == OS.LINUX: shell_info['uname -a'] = self.run_single_shell_cmd( shell, 'uname -a') shell_info['ifconfig'] = self.run_single_shell_cmd( shell, 'ifconfig') if os == OS.WINDOWS: shell_info['ipconfig'] = self.run_single_shell_cmd( shell, 'ipconfig') return shell_info def run_single_shell_cmd(self, shell, cmd): shell.write(cmd) try: time.sleep(WAIT_INTERVAL_FOR_SHELL_RESPONSE) shell_response = shell.read().strip() if shell_response != '': self.console.info( "Received shell response to command '{}'.".format(cmd)) self.console.debug("\t {}".format(shell_response)) return shell_response else: self.console.info( "Received empty shell response to command '{}'.".format( cmd)) return None except KeyError: self.console.error( "Unable to read data from console for command '{}'.".format( cmd)) return None @staticmethod def only_empty_replies(shell_info): for cmd in shell_info: if shell_info.get(cmd) is not None: return False return True def exit_shell(self, shell): shell.write('exit') def downgrade_meterpreter(self, meterpreter_session): meterpreter_session.write('shell') time.sleep(WAIT_INTERVAL_FOR_SHELL_RESPONSE) meterpreter_session.read() # close a single session so that pyperpwn does not get confused while future executions of this exploit def close_single_session(self, session_id): self.exit_shell( self.msf_handler.get_session_obj(session_id=str(session_id))) self.console.info( "Automatically closed session with id {}".format(session_id)) # close all open remote sessions def close_all_sessions(self): sessions = self.msf_handler.get_all_sessions() for session_id in sessions: self.exit_shell( self.msf_handler.get_session_obj(session_id=session_id)) self.console.info("Automatically closed {} sessions.".format( len(sessions)))
class ExploitExecutor: def __init__(self, msf_handler, wizard): self.console = ConsoleHandler('ExploitExec') self.msf_handler = msf_handler self.wizard = wizard # checks if non-common params are existent and returns a dict of new values in case they should be changed def check_exploit_params(self, expl): non_common = expl.get_non_common_params() self.console.debug("\t all required params {}".format(expl.req_params)) self.console.debug("\t non-common params {}".format(non_common)) params = {} if len(non_common) > 0: check = self.console.prompt( "Exploit has {} non-common params. Do you want to change them? [y/n]" .format(len(non_common))) current_expl = self.msf_handler.get_exploit(expl.path) if check == 'y': for param in non_common: current_val = current_expl[param] new_val = self.wizard.read_param_value_from_console( param, current_val) if new_val is not None: params[param] = new_val else: self.console.debug("apparently no change is demanded") return params # execute an exploit with the given parameters def execute_exploit(self, expl, params, additional_params, express=False, execall=False): execute = False if not ExploitExecutor.is_private_address(params.get('rhost')): if self.wizard.exec_expl_against_public_addr(params.get('rhost')): execute = True elif express or execall: execute = True elif self.wizard.exec_expl(expl_path=expl.path, rhost=params.get('rhost')): execute = True if execute: exploit = self.get_prepared_exploit(expl.path, params, additional_params) payload = self.get_prepared_payload(exploit, params.get('lhost'), params.get('lport'), express=express) is_reverse_payload = PayloadUtils.is_reverse_payload( payload=payload) is_staged_payload = PayloadUtils.is_staged_payload(payload=payload) result = [] conn_supervisor = ConnectionSupervisor( lhost=params.get('lhost'), rhost=params.get('rhost'), is_reverse_payload=is_reverse_payload, is_staged_payload=is_staged_payload, results=result, rport=params.get('rport'), lport=params.get('lport')) conn_supervisor.start() self.console.info("Set payload '{}'".format(payload.modulename)) cid = self.msf_handler.get_new_console_with_id() self.console.info( "Exploit is being executed using msf console {}. This might take a while." .format(cid)) output = self.msf_handler.execute_exploit_with_output( cid=cid, exploit=exploit, payload=payload) self.console.info("Received exploit output") self.console.debug(output) conn_supervisor.join() self.console.debug( "ConnectionInspector detection methods: {}".format(result)) if output is None: self.console.error("Unable to execute exploit!") else: self.console.info("Exploit has been run without errors.") return {'output': output, 'detection_method': result} else: return {'output': None, 'detection_method': None} return None # get exploit object defined by 'path' with all delivered params set def get_prepared_exploit(self, path, params, additional_params): exploit = self.msf_handler.get_exploit(path) try: self.set_module_param('exploit', exploit, 'RHOST', params['rhost']) except KeyError: self.set_module_param_safely('exploit', exploit, 'RHOSTS', params['rhost']) self.set_module_param_safely('exploit', exploit, 'RPORT', params['rport']) self.set_module_param_safely('exploit', exploit, 'VERBOSE', True) for key in additional_params.keys(): self.set_module_param_safely('exploit', exploit, key, additional_params.get(key)) if len(exploit.missing_required) > 0: self.console.warn( "Exploit might fail because of missing parameter values: {}". format(exploit.missing_required)) return exploit # get the payload object to be used for execution def get_prepared_payload(self, expl, lhost, lport, express=False): payload_name = None if not express: payload_name = self.wizard.get_payload(payload_paths=expl.payloads) if payload_name is None: payload_name = PayloadUtils.get_prioritized_payload(expl.payloads) self.console.info("\t Selected payload: {}".format(payload_name)) payload = self.msf_handler.get_payload(payload_path=payload_name) self.console.debug("Payload requires those params: {}".format( payload.required)) if 'LPORT' in payload.required: self.set_module_param_safely('payload', payload, 'LPORT', lport) if 'LHOST' in payload.required: self.set_module_param_safely('payload', payload, 'LHOST', lhost) return payload # set a single param value for the given module, don't catch errors def set_module_param(self, module_type, module_obj, param_name, param_val): module_obj[param_name] = param_val self.console.debug("\t setting {} param {} to {}".format( module_type, param_name, param_val)) # set a single param and catch errors def set_module_param_safely(self, module_type, module_obj, param_name, param_val): try: self.set_module_param(module_type, module_obj, param_name, param_val) except KeyError as e: self.console.warn("Failed to set module param: ".format(e)) # checks if the given IP address is private, returns true if yes @staticmethod def is_private_address(ip): ip_parts = ip.split('.') for i, _ in enumerate(ip_parts): ip_parts[i] = int(ip_parts[i]) if ip_parts[0] == 10: return True if ip_parts[0] == 172 and 16 <= ip_parts[1] <= 31: return True if ip_parts[0] == 192 and ip_parts[1] == 168: return True return False
class MsfHandler: def __init__(self, host, port, pwd): self.console = ConsoleHandler('MsfHandler') self.msfrpc_client = self.get_msfrpc_client(host=host, port=port, pwd=pwd) if self.msfrpc_client is None: raise ConnectionError('Cannot connect to MSFRPC') # start the client and return it def get_msfrpc_client(self, host, port, pwd): self.console.info("Connecting to MSFRPC server...") msf_client = None try: msf_client = MsfRpcClient(password=pwd, port=port, server=host, ssl=False) self.console.info( "\t Successfully connected to MSFRPC at {}:{}".format( host, port)) msf_console = MsfRpcConsole(msf_client, cb=MsfHandler.read_output) except (ConnectionRefusedError, NewConnectionError, MaxRetryError, requests.exceptions.ConnectionError) as e: self.console.error( "\t Connection to MSFRPC at {}:{} couldn't be established: {}!" .format(host, port, type(e).__name__)) except MsfAuthError: self.console.error("MSFRPC Authentication error.") except MsfRpcError: self.console.error("\t Error while logging in to MSFRPC!") return msf_client @staticmethod def read_output(console_data): console = ConsoleHandler('MsfHandler') console.debug("Main console received output: {}".format(console_data)) def get_all_exploits(self): return self.msfrpc_client.modules.exploits # get reference to metasploit exploit object def get_exploit(self, expl_path): try: expl = self.msfrpc_client.modules.use('exploit', expl_path) return expl except (UnicodeDecodeError, requests.exceptions.ConnectionError): ConsoleHandler("ExploitParser").error( "\t error decoding exploit module") return None def get_payload(self, payload_path): return self.msfrpc_client.modules.use('payload', payload_path) def execute_exploit_with_output(self, cid, exploit, payload): return self.msfrpc_client.consoles.console(cid).run_module_with_output( exploit, payload=payload) def get_new_console_with_id(self): return self.msfrpc_client.consoles.console().cid def get_all_sessions(self): return self.msfrpc_client.sessions.list def get_session_dict(self, session_id): return self.get_all_sessions()[session_id] def get_session_obj(self, session_id): return self.msfrpc_client.sessions.session(session_id)
class DBHandler: def __init__(self, msf_handler): self.msf_handler = msf_handler self.console = ConsoleHandler("DBHandler") self.exploits = [] self.db_client = MongoClient(db_config.get("host"), db_config.get("port")) self.db = self.db_client.pyperpwn self.expl_coll = self.db.exploits self.cve_coll = self.db.cve self.vuln_coll = self.db.vulns self.expl_class_coll = self.db.classifications self.exec_status_coll = self.db.exec_status self.check_db_connection() def check_db_connection(self): self.console.info("Checking connection to MongoDB...") try: self.db_client.server_info() except ServerSelectionTimeoutError: self.console.error( "Unable to connect to MongoDB! Please check if it is running and reachable on {}:{}. Aborting..." .format(db_config.get("host"), db_config.get("port"))) raise ConnectionError('cannot connect to MongoDB.') # remove all cached data that is not necessary for a clean start def clear_db_on_start(self): self.console.debug("Remove cached data from DB...") self.db.vulns.remove({}) self.db.exec_status_coll.remove({}) # remove all data that has been cached in the DB def remove_cached_data(self): self.expl_class_coll.remove({}) # checks if a refill of the exploit collection is necessary and if yes, performs it def build_expl_coll(self): self.console.info("Checking state of Exploit Cache...") if self.check_db(): self.console.info("\t No need to fill cache again. Proceeding...") return self.console.info("\t Remove orphaned exploits...") self.expl_coll.delete_many({}) self.console.info("\t Start caching exploits...") fail_count = 0 i = 0 for expl_name in self.msf_handler.get_all_exploits(): expl = self.get_exploit_obj_by_name(expl_name) if expl is None: fail_count += 1 else: self.expl_coll.insert_one(expl) i += 1 self.expl_coll.create_index([('search_name', pymongo.TEXT)], name='search_index', default_language='english') self.console.info( "Successfully built cache with {} entries. Faced {} errors".format( i - fail_count, fail_count)) # creates an exploit object from the MSF data related to the given exploit path def get_exploit_obj_by_name(self, expl_path): expl = self.msf_handler.get_exploit(expl_path) cve = DBHandler.get_module_attribute('CVE', expl.references) bid = DBHandler.get_module_attribute('BID', expl.references) full_name = expl._info['name'] search_name = Exploit.improve_name(full_name) rank = expl._info['rank'] os = DBHandler.get_os_from_expl_name(expl_path) expl_obj = Exploit(path=expl_path, full_name=full_name, search_name=search_name, os=os, rank=rank, cve=cve, bid=bid, all_params=expl.options, req_params=expl.required, score=0.0) expl_dict = expl_obj.to_dict() return expl_dict # check if the DB contains almost the same amount of exploits as MSF def check_db(self): db_count = self.expl_coll.count_documents({}) msf_count = len(self.msf_handler.get_all_exploits()) self.console.info( "\t Found {} exploits in DB, while MSF currently offers {} in total" .format(db_count, msf_count)) if db_count + db_config.get("diff_range") >= msf_count: return True return False @staticmethod def get_module_attribute(tag, attribute_list): for elem in attribute_list: if elem[0] == tag: return elem[1] return "" @staticmethod def get_os_from_expl_name(expl_name): expl_name = expl_name.split("/") return expl_name[0] def search_by_cve(self, cve): matching_expl = [] for expl in self.expl_coll.find({"cve": cve}): expl_obj = Exploit.from_dict(expl) matching_expl.append(expl_obj) return matching_expl def search_by_bid(self, bid): matching_expl = [] for expl in self.expl_coll.find({"bid": bid}): expl_obj = Exploit.from_dict(expl) matching_expl.append(expl_obj) return matching_expl # search the DB for all exploits with a matching name (textScore) def search_by_name(self, name): matching_expl = [] for expl in self.expl_coll.find({'$text': { '$search': name }}, {'score': { '$meta': 'textScore' }}): expl_obj = Exploit.from_dict(expl) matching_expl.append(expl_obj) return matching_expl # # methods for the CVE details collection # def add_cve_details(self, cve): self.cve_coll.insert_one(cve) def get_cve(self, cve): return self.cve_coll.find_one({'cve': cve}) # # methods for the exploits' classifications collection # def add_expl_class(self, expl_class): self.expl_class_coll.insert_one(expl_class.to_dict()) def get_expl_class(self, expl_path): classification_obj = self.expl_class_coll.find_one( {'expl_path': expl_path}) if classification_obj is None: return None return getattr(ExploitClass, classification_obj.get('class', None).upper(), None) # # methods for application's execution status # def save_exec_status(self, status): status.ended = time.time() self.exec_status_coll.insert_one(status.to_dict()) self.console.info("Successfully saved execution status to DB...") def take_matching_exec_status(self, ip): status_dict = self.exec_status_coll.find_one({'ip': ip}) if status_dict is None: return None self.exec_status_coll.delete_one({'_id': status_dict.get('_id')}) return ExecStatus.from_dict(status_dict) # # methods for vulnerabilities collection # def save_vulns(self, vulns): for vuln in vulns: self.vuln_coll.insert_one(vuln.to_dict()) def get_vulns(self): vuln_objs = [] vulns = self.vuln_coll.find({}).sort('cvss', pymongo.DESCENDING) for vuln in vulns: vuln_obj = Vulnerability.from_dict(vuln) vuln_objs.append(vuln_obj) return vuln_objs
class Wizard: def __init__(self): self.print_greeting() self.console = ConsoleHandler("Wizard") def get_options(self): try: args = self.parse_args() args['vuln_source'] = self.check_delivered_value( args['vuln_source'], "Vulnerability Scanner Report Path", (lambda x: True)) args['expl_class'] = self.check_delivered_value( args['expl_class'], "Exploit Classification File Path", (lambda x: True)) args['rhost'] = self.check_delivered_value( args.get('rhost'), "Remote Host IP Address", self.check_is_ip_addr) args['lhost'] = self.check_delivered_value( args.get('lhost'), "Local Host IP Address", self.check_is_ip_addr) args['expl_rank'] = self.check_delivered_value( args.get('expl_rank'), "Exploit minimum rank", (lambda x: Rank.is_valid(x))) args['expl_os'] = self.check_delivered_value( args.get('expl_os'), "Rhost operating system", (lambda x: OS.is_valid(x))) args['xspeed'] = self.check_delivered_value( int(args.get('xspeed')), "Execution speed", (lambda x: x in [1, 2, 3])) args['xspeed'] = Speed(args['xspeed']) args['espeed'] = self.check_delivered_value( int(args.get('espeed')), "Evaluation speed", (lambda x: x in [1, 2, 3])) args['espeed'] = Speed(args['espeed']) args['lport'] = int(args['lport']) self.console.debug(args) if args.get('express'): self.console.warn( "EXPRESS MODE IS ENABLED. Only as few prompts as necessary will appear. Please note that using the default values might lead to worse results." ) return args except ValueError: raise ValueError @staticmethod def parse_args(): parser = ArgumentParser( description= "pyperpwn - Tool to verify the success of Metasploit's exploits", epilog='With great power comes great responsibility. Use with care.' ) parser.add_argument("-p", "--password", dest="msfrpc_pwd", help="Connect to MSFRPCD using this password", metavar="pwd", required=True) parser.add_argument("-a", "--msfhost", dest="msfrpc_host", help="Connect to MSFRPCD at this IP address", metavar="ip", default=msf_default_config.get("host")) parser.add_argument("-P", "--port", dest="msfrpc_port", help="Connect to MSFRPCD at the specified port", metavar="port", default=msf_default_config.get("port")) parser.add_argument( "-o", "--os", dest="expl_os", help= "Use only exploits developed for this OS. Currently supports 'linux' and 'windows' and 'all'.", metavar="name") parser.add_argument('--no-multi', dest='expl_os_multi', action='store_false', help="Do not use universal exploits") parser.set_defaults(expl_os_multi=True) parser.add_argument("-r", "--rank", dest="expl_rank", help="Use only exploits with this minimum rank", metavar="min-rank", default="manual") parser.add_argument("-v", "--vulns", dest="vuln_source", help="Read this report of a vulnerability scanner", metavar="path") parser.add_argument( "-c", "--class", dest="expl_class", help="Read this file containing an exploit classification", metavar="path") parser.add_argument("-x", "--export", dest="report_path", help="Save the output report at this location", metavar="path", default="hyper_pyper_report.csv") parser.add_argument("-t", "--target", dest="rhost", help="IP address of the remote host to be tested", metavar="ip") parser.add_argument( "-l", "--lhost", dest="lhost", help="IP address of the local host running this script", metavar="ip") parser.add_argument("-w", "--lport", dest="lport", help="Port number to be used for Session Handlers", metavar="port", default="5678") parser.add_argument( '-e', dest='exec_expl', action='store_true', help="Only actually execute exploits if this flag is set.") parser.add_argument( "-xs", "--execspeed", dest="xspeed", help= "The speed level to be used for exploit execution. Can be either 1, 2 or 3. (the higher, the faster)", metavar="1|2|3", default="3") parser.add_argument( "-es", "--evalspeed", dest="espeed", help= "The speed level to be used for success evaluation. Can be either 1, 2 or 3. (the higher, the faster)", metavar="1|2|3", default="3") parser.add_argument( "-XX", "--express", dest="express", action='store_true', help= "Execute the tool using default values and without any prompts.") parser.add_argument( "-XA", "--execall", dest="execall", action='store_true', help="Don't ask whether exploits should be executed.", ) parser.set_defaults(exec_expl=False) parser.set_defaults(express=False) parser.set_defaults(execall=False) args = vars(parser.parse_args()) return args # check if the provided param value is valid, judging by the given validation function def check_delivered_value(self, val, name, validation_fun): if val is None: val = self.read_option_value(name) if validation_fun(val): self.console.debug("setting '{}' to '{}'".format(name, val)) return val self.console.error("\t no valid value provided for '{}'".format(name)) raise ValueError def read_option_value(self, option_name): param_value = self.console.prompt( "Required option [{}] not specified. Enter a value".format( option_name)) if len(param_value) == 0: self.console.error("Cannot work without this parameter...") return param_value @staticmethod def check_is_ip_addr(ip): parts = ip.split('.') if len(parts) == 4: for part in parts: if int(part) < 0 or int(part) > 255: return False return True return False # return the path of the manually selected payload, or None for default payload def get_payload(self, payload_paths): change_payload = self.console.prompt( "Do you want to change the default payload ({} alternatives available)? [y/n]" .format(len(payload_paths) - 1)) if change_payload == 'y': self.console.info("\t Available payloads:") for i in range(len(payload_paths)): self.console.info("\t ({})\t{}".format(i, payload_paths[i])) try: new_payload = int( self.console.prompt( "Enter the number of the payload to use instead (blank for default)" )) except ValueError: self.console.debug( "No number provided. Default payload will be used.") return None if new_payload in range(len(payload_paths)): return payload_paths[new_payload] self.console.warn( "Invalid value provided. Default payload will be used.") return None # prompt the user for a new value for a given module param def read_param_value_from_console(self, param_name, current_value): param_value = self.console.prompt( "\t [{}], current value: '{}'. Enter a new value (leave blank for default)" .format(param_name, str(current_value))) if len(param_value) > 0: self.console.debug("\t setting param {} = {}".format( param_name, param_value)) return param_value return None def continue_last_exec(self, ip, exec_state): if exec_state is None: self.console.debug( 'No matching previous unfinished execution found.') return False self.console.info( "Found an unfinished execution against {}, started at {}".format( ip, time.strftime('%Y-%m-%d %H:%M %Z', time.localtime(exec_state.start_time)))) continue_exec = self.console.prompt( 'Do you want to continue? "n" discards the data. [y/n]') == 'y' if continue_exec is True: self.console.debug( 'Continue previously unfinished execution {}'.format( exec_state)) return continue_exec def generate_report_now(self): return self.console.prompt( "Save this execution for later [c]ontinuation, [g]enerate a report now or e[x]it at any cost?" ) def exec_expl_against_public_addr(self, rhost): return self.console.prompt( "Detected public IP address. Do you want to execute the exploit against {}? [y/n]" .format(rhost)) == 'y' def exec_expl(self, expl_path, rhost): return self.console.prompt( "Do you want to execute exploit '{}' against {}? [y/n]".format( expl_path, rhost)) == 'y' def print_expl_exec_info(self, expl_path, is_repetition): if not is_repetition: self.console.empty(1) self.console.caption( "Exploit '{}' will now be executed".format(expl_path)) else: self.console.empty(1) self.console.warn( "Exploit '{}' will be re-executed since it hasn't been successful." .format(expl_path)) def print_greeting(self): print( "\n====================================================================================\n" ) print( " $$$$$$\ $$\ $$\ $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ $$\ $$$$$$$\ \n" + "$$ __$$\ $$ | $$ |$$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ $$ | $$ | $$ |$$ __$$\ \n" "$$ / $$ |$$ | $$ |$$ / $$ |$$$$$$$$ |$$ | \__|$$ / $$ |$$ | $$ | $$ |$$ | $$ |\n" "$$ | $$ |$$ | $$ |$$ | $$ |$$ ____|$$ | $$ | $$ |$$ | $$ | $$ |$$ | $$ |\n" "$$$$$$$ |\$$$$$$$ |$$$$$$$ |\$$$$$$$\ $$ | $$$$$$$ |\$$$$$\$$$$ |$$ | $$ |\n" "$$ ____/ \____$$ |$$ ____/ \_______|\__| $$ ____/ \_____\____/ \__| \__|\n" "$$ | $$\ $$ |$$ | $$ | \n" "$$ | \$$$$$$ |$$ | $$ | \n" "\__| \______/ \__| \__| \n" ) print( "====================================================================================\n" ) print( "Welcome to pyperpwn!\nPlease restart the MSFRPC daemon everytime you restart this application.\n" ) print( "====================================================================================\n" ) def print_vuln_caption(self, vuln_name, cve, cvss): self.console.empty(2) self.console.caption( "NOW TREATING VULNERABILITY: '{}' (CVSS: {})".format( vuln_name, cvss)) self.console.empty(1)
def post_session_actions(details, session): details.execution_detection_method.append(ExplExecDetectionMethod.DETECTED_SHELL) details.session_info = session_handler.gather_info(session, os=args.get('expl_os')) if not SessionHandler.only_empty_replies(details.session_info): console.debug("not all session replies are empty") details.execution_detection_method.append(ExplExecDetectionMethod.COMMUNICATED_WITH_SHELL) if __name__ == '__main__': args = None if sys.version_info[0] < 3: raise EnvironmentError('Pyperpwn requires Python v3') try: wizard = Wizard() try: args = wizard.get_options() except ValueError as err: console.error(err) instant_exit(friendly=False) try: main(args) except ConnectionError: instant_exit(friendly=False) except KeyboardInterrupt: if args is not None: graceful_exit() else: console.error("cannot create report because of missing input.") instant_exit(friendly=True)
class SuccessChecker: def __init__(self): self.console = ConsoleHandler("SuccessCheck") def check(self, speed, rhost, port, uri): res = [] iterations = EvalSpeed[speed.name].value[0] interval = EvalSpeed[speed.name].value[1] for iteration in range(iterations): self.console.info("Run #{} of success checker".format(iteration)) check_result = {'ping': self.ping_check(rhost)} check_result['nmap'] = self.nmap_check(host=rhost, port=port) if uri is not None: url = "{}:{}{}".format(rhost, str(port), uri) check_result['http'] = self.http_check(url=url) res.append(check_result) self.console.info("\t intentionally waiting...") time.sleep(interval) self.console.debug(res) return res def ping_check(self, rhost): self.console.debug("\t start PING checker") param = '-n' if platform.system().lower() == 'windows' else '-c' command = ['ping', param, '1', rhost] with open(os.devnull, 'w') as DEVNULL: res = subprocess.call(command, stderr=DEVNULL, stdout=DEVNULL) if res == 0: self.console.debug("\t \t host still up and running.") return ServiceStatus.UP_ACCESSIBLE else: self.console.debug("\t \t host dead.") return ServiceStatus.DOWN def http_check(self, url): self.console.debug("\t start HTTP checker for {}".format(url)) command = ['curl', url, "-m", "{}".format(HTTP_CHECK_MAX_TIME)] with open(os.devnull, 'w') as DEVNULL: res = subprocess.call(command, stderr=DEVNULL, stdout=DEVNULL) if res == 0: self.console.debug("\t \t web server still up and running.") return ServiceStatus.UP_ACCESSIBLE else: self.console.info("\t \t web server dead.") return ServiceStatus.DOWN def nmap_check(self, host, port): self.console.debug("\t start nmap checker...") scanner = nmap.PortScanner() scanner.scan(hosts=host, ports=str(port)) info = scanner.scaninfo() err = info.get('error') try: del info['error'] except KeyError: pass self.console.debug(info) if err is not None: self.console.debug("\t \t {}".format(err)) try: state = scanner[host]['tcp'][port]['state'] self.console.debug("\t \t -> Port is {}".format(state)) except KeyError: self.console.warn("\t \t unable to retrieve nmap output: {}.".format(info)) return None try: state_obj = PortStatus[state.strip().upper()] except KeyError: self.console.error("Invalid value for Port State: {}".format(state)) return PortStatus.CLOSED return state_obj