示例#1
0
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))
示例#3
0
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
示例#4
0
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)))
示例#5
0
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
示例#6
0
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)
示例#7
0
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
示例#8
0
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)
示例#9
0
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)
示例#10
0
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