Example #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))
Example #3
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)))
Example #4
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
Example #5
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)
Example #6
0
class ConnectionSupervisor(Thread):
    proc = None

    detected_syn = False
    detected_synack = False
    syn_seq = -1
    synack_seq = -1

    detected_exploit_delivery = False
    detected_payload_delivery = False
    detected_payload_connection = False

    expl_exec_detection_method: ExplExecDetectionMethod

    def __init__(self, lhost, rhost, rport, lport, is_reverse_payload, is_staged_payload, results):
        Thread.__init__(self)
        self.console = ConsoleHandler('ConnSuperv')
        if lhost is None or rhost is None or lhost == '' or rhost == '':
            raise AttributeError('You need to specify a value for rhost and lhost.')
        self.lhost = lhost
        self.rhost = rhost
        self.rport = rport
        self.lport = lport
        self.is_reverse_payload = is_reverse_payload
        self.is_staged_payload = is_staged_payload
        self.file = open("tcpdump_log.txt", "w")
        self.results = results

    # Start the subprocess
    def run(self):
        self.console.info("Start to capture network traffic with host {}...".format(self.rhost))
        self.proc = subprocess.Popen(['tcpdump', '-l', 'host', self.rhost, '-nn', '-S'],
                                     stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        # -nn = don't convert addresses and port numbers to names
        # -S = print absolute sequence numbers
        for row in iter(self.proc.stdout.readline, b''):
            line_str = row.rstrip().decode("utf-8")
            self.console.debug(line_str)
            self.write_to_log(line_str)
            request = self.parse_tcpdump_line(line_str)
            connection = self.detect_established_connection(request, self.lhost, self.rhost)
            successful_exec = None
            reverse_payload_req = self.detect_reverse_payload_request(request)
            self.append_to_results(reverse_payload_req)
            dropper_req = self.detect_dropper_request(request)
            self.append_to_results(dropper_req)
            if connection is not None:
                self.console.debug("detected established connection with direction: {}".format(connection.direction))
                successful_exec = self.detect_successful_exec(connection=connection, rport=self.rport, lport=self.lport)
            if (successful_exec is not None and successful_exec != ExplExecDetectionMethod.NONE) or \
                    reverse_payload_req is not None or dropper_req is not None:
                self.append_to_results(successful_exec)
                self.console.warn("EXPLOIT HAS BEEN EXECUTED ON THE TARGET!")
                self.console.info("\t detected with: {}".format(self.results[-1].name))
                self.write_to_log("detected: ".format(self.results[-1].name))

    # Stop the subprocess
    def join(self, timeout=None):
        self.console.info("Stop capturing network traffic...")
        if self.proc is not None:
            self.console.debug("killing subprocess with PID {}...".format(self.proc.pid))
            self.proc.terminate()
            self.console.debug("killed...")
        self.file.close()
        Thread.join(self)

    # reset all connection state flags
    def reset_conn_state(self):
        self.detected_syn = False
        self.detected_synack = False
        self.syn_seq = -1
        self.synack_seq = -1

    # reset all execution state flags
    def reset_exec_state(self):
        self.detected_exploit_delivery = False
        self.detected_payload_delivery = False
        self.detected_payload_connection = False

    @staticmethod
    def remove_redundant_chars(val):
        if val[:1] == '[':
            val = val[1:]
        if val[-1:] == ':' or val[-1:] == ']':
            val = val[:-1]
        return val

    # parse a line of tcpdump and return a Request object or (in case of errors) an empty dict
    def parse_tcpdump_line(self, line):
        line = line.split(', ')
        params = []
        for item in line:
            if item[:7] == 'options':
                params.append('options')
                params.append(item[8:])
            else:
                for elem in item.split(' '):
                    params.append(elem)
        for i in range(len(params)):
            params[i] = self.remove_redundant_chars(params[i])
        if len(params) >= 7:
            request = {'time': params[0], 'protocol': params[1]}
            if request.get('protocol') == 'IP':
                request['source_ip'] = ConnectionSupervisor.extract_ip_address(params[2])
                request['source_port'] = ConnectionSupervisor.extract_port_number(params[2])
                request['target_ip'] = ConnectionSupervisor.extract_ip_address(params[4])
                request['target_port'] = ConnectionSupervisor.extract_port_number(params[4])
                request['flags'] = params[6]
                count = 7
                while count + 2 <= len(params):
                    val = params[count + 1]
                    try:
                        val = int(val)
                    except ValueError:
                        val = str(val)
                    request[params[count]] = val
                    count += 2
            return request
        return {}

    @staticmethod
    def extract_ip_address(addr_str):
        elems = addr_str.split(".")
        return "{}.{}.{}.{}".format(elems[0], elems[1], elems[2], elems[3])

    @staticmethod
    def extract_port_number(addr_str):
        try:
            return int(addr_str.split(".")[4])
        except (ValueError, IndexError):
            return -1

    # determine the direction of a given request
    def get_request_direction(self, conn, lhost, rhost):
        if lhost == conn.get('source_ip') and rhost == conn.get('target_ip'):
            return ConnectionDirection.HOST_TO_TARGET
        if lhost == conn.get('target_ip') and rhost == conn.get('source_ip'):
            return ConnectionDirection.TARGET_TO_HOST
        return ConnectionDirection.UNDETERMINED

    # returns a Connection object as soon as a successful TCP handshake has been detected
    def detect_established_connection(self, request, lhost, rhost):
        flags = request.get('flags')
        if flags == 'S':
            self.detected_syn = True
            self.syn_seq = request.get('seq')
            self.console.debug("...detected SYN")
        if flags == 'S.' and self.detected_syn and request.get('ack') == self.syn_seq + 1:
            self.detected_synack = True
            self.synack_seq = request.get('seq')
            self.console.debug("...detected SYNACK")
        if flags == '.' and self.detected_syn and self.detected_synack and request.get('ack') == self.synack_seq + 1:
            self.console.debug("...detected ACK")
            direction = self.get_request_direction(conn=request, lhost=lhost, rhost=rhost)
            self.reset_conn_state()
            self.console.debug(request)
            connection = Connection(direction=direction, client_ip=request.get('source_ip'),
                                    client_port=request.get('source_port'), server_ip=request.get('target_ip'),
                                    server_port=request.get('target_port'))
            return connection
        return None

    # detect by the given execution state and a new connection whether the exploit has already been successfully executed
    def detect_successful_exec(self, connection, rport, lport):
        self.console.debug("lport: {}, rport: {}".format(lport, rport))
        self.console.debug(connection)
        # error case
        if connection is None or connection.direction == ConnectionDirection.UNDETERMINED:
            return ExplExecDetectionMethod.NONE
        # no matter what kind of payload, the exploit needs to be delivered first
        if not self.detected_exploit_delivery and connection.direction == ConnectionDirection.HOST_TO_TARGET and connection.server_port == rport:
            self.detected_exploit_delivery = True
            self.console.info("Detected exploit delivery to target")
            return ExplExecDetectionMethod.NONE
        # to detect payload connection for single payloads:
        if not self.is_staged_payload and self.detected_exploit_delivery:
            # for single bind payloads
            if connection.direction == ConnectionDirection.HOST_TO_TARGET and not self.is_reverse_payload:  # and lport == connection.server_port
                self.console.info("Detected single bind payload connection")
                return ExplExecDetectionMethod.BIND_PAYLOAD_CONNECTION
            # for single reverse payloads
            if connection.direction == ConnectionDirection.TARGET_TO_HOST and self.is_reverse_payload and lport == connection.server_port:
                self.console.info("Detected single reverse payload connection")
                return ExplExecDetectionMethod.REVERSE_PAYLOAD_CONNECTION
        # to detect the delivery of the stage for staged payloads
        if self.is_staged_payload and self.detected_exploit_delivery and not self.detected_payload_delivery:
            if connection.direction == ConnectionDirection.TARGET_TO_HOST:
                self.detected_payload_delivery = True
                self.console.info("Reverse payload has been delivered")
                return ExplExecDetectionMethod.PAYLOAD_STAGE_CONNECTION
        # to detect the payload connection for staged payloads
        if self.detected_payload_delivery and self.is_staged_payload:
            if connection.direction == ConnectionDirection.HOST_TO_TARGET and not self.is_reverse_payload and lport == connection.server_port:
                self.console.info("Detected staged bind payload")
            if connection.direction == ConnectionDirection.TARGET_TO_HOST and self.is_reverse_payload and lport == connection.server_port:
                self.console.info("Detected staged reverse payload connection")
            return ExplExecDetectionMethod.PAYLOAD_STAGE_CONNECTION
        return ExplExecDetectionMethod.NONE

    # detect a connection attempt from a reverse payload by the given request
    def detect_reverse_payload_request(self, request):
        try:
            if self.detected_exploit_delivery and self.is_reverse_payload and request['source_ip'] == self.rhost and \
                    request['target_ip'] == self.lhost and request['target_port'] == self.lport and \
                    request.get('flags') == 'S':
                self.console.info("Detected reverse payload request.")
                return ExplExecDetectionMethod.REVERSE_PAYLOAD_REQUEST
        except KeyError:
            pass
        return None

    # detect a connection attempt from the payload's dropper
    def detect_dropper_request(self, request):
        try:
            if self.detected_exploit_delivery and self.is_staged_payload and request['source_ip'] == self.rhost and \
                    request['target_ip'] == self.lhost and not self.detected_payload_connection and \
                    request.get('flags') == 'S' and request['target_port'] != self.lport:
                self.console.info("Detected request from the payload dropper")
                return ExplExecDetectionMethod.PAYLOAD_STAGE_REQUEST
        except KeyError:
            pass
        return None

    def append_to_results(self, detection_method):
        if detection_method is not None and detection_method is not ExplExecDetectionMethod.NONE:
            self.results.append(detection_method)

    # for debugging
    def write_to_log(self, line):
        try:
            self.file.write('{} \n'.format(line))
        except ValueError:
            pass
Example #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
Example #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)
Example #9
0
class ExploitMatcher:

    def __init__(self, db_handler):
        self.console = ConsoleHandler("ExplMatcher")
        self.db_handler = db_handler

    def find_exploits_for_vuln(self, vuln, os, os_multi, rank):
        matching_expl = self.get_all_exploits_for_name(vuln.name, os, os_multi) + \
                        self.find_exploits_by_cve(vuln.cve) + \
                        self.find_exploits_by_bid(vuln.bid)
        if len(matching_expl) > 0:
            matching_expl = self.remove_duplicates(matching_expl)
            matching_expl = self.filter_by_os(os, os_multi, matching_expl)
            self.console.info("Filtered exploits by OS '{}': {} left.".format(os, len(matching_expl)))
            matching_expl = self.filter_by_rank(rank, matching_expl)
            self.console.info("Filtered exploits by Rank '{}': {} left.".format(rank, len(matching_expl)))
            matching_expl = self.sort_exploits(matching_expl)
        return matching_expl

    def find_exploits_by_cve(self, cve):
        cves = CVEDetailParser.parse_cves(self.console, cve)
        cve_expl = []
        self.console.info("Searching by CVEs {}".format(cves))
        if len(cves) > 0:
            for cve in cves:
                expl_list = self.get_all_exploits_for_cve(cve)
                if expl_list is not None:
                    cve_expl.append(expl_list)
        if len(cve_expl) == 0:
            self.console.warn("\t None found.")
        return cve_expl

    def find_exploits_by_bid(self, bid_str):
        bids = CVEDetailParser.parse_bids(bid_str)
        bid_expl = []
        self.console.info("Searching by BIDs {}".format(bids))
        if len(bids) > 0:
            for bid_str in bids:
                expl = self.get_all_exploits_for_bid(bid_str)
                if expl is not None:
                    bid_expl.append(expl)
        if len(bid_expl) == 0:
            self.console.warn("\t None found.")
        return bid_expl

    # actually perform search for exploits by CVE
    def get_all_exploits_for_cve(self, cve):
        expl_list = self.db_handler.search_by_cve(cve)
        if len(expl_list) > 0:
            self.console.info("\t Found {} exploit(s).".format(len(expl_list)))
            return expl_list
        return None

    # actually perform search for exploits by BID
    def get_all_exploits_for_bid(self, bid):
        if bid == '':
            return None
        expl_list = self.db_handler.search_by_bid(bid)
        if len(expl_list) > 0:
            self.console.info("\t Found {} exploit(s).".format(len(expl_list)))
            return expl_list
        return None

    # actually perform search for exploits by vulnerability name
    def get_all_exploits_for_name(self, name, os, multi):
        search_name = Exploit.improve_name(name)
        self.console.info("Searching by keywords '{}'".format(search_name))
        expl_list = self.db_handler.search_by_name(search_name)
        expl_list = list(filter(lambda expl: expl.score >= exploit_search_config.get('min_score'), expl_list))
        expl_list.sort(key=lambda x: float(x.score), reverse=True)
        if len(expl_list) > exploit_search_config.get('max_count_by_name'):
            self.console.debug("found too many exploits, only use the {} with the highest score.".format(
                exploit_search_config.get('max_count_by_name')))
            expl_list = self.filter_by_os(os=os, multi=multi, expl_list=expl_list)
            expl_list = expl_list[:exploit_search_config.get('max_count_by_name')]
        for expl in expl_list:
            self.console.debug(
                "\t \t score: {}, path: '{}', title: '{}'".format(expl.score, expl.path, expl.search_name))
        if len(expl_list) > 0:
            self.console.info("\t Found {} exploit(s).".format(len(expl_list)))
        else:
            self.console.warn("\t None found.")
        return expl_list

    # extrahiere Versionsnummer aus String, findet Nummern mit bis zu dreistufiger Hierarchie
    @staticmethod
    def get_version_number(str):
        pattern = "(\d)+.(\d)(.(\d)+)?"
        match = re.search(pattern, str)
        if match is None:
            return None
        return match.group(0)

    # removes all duplicates from a nested list of exploit objects
    def remove_duplicates(self, expl_lists):
        if expl_lists is None:
            return None
        expl_list = Exploit.flatten_expl_lists(expl_lists)
        filtered_expl_list = []
        for expl in expl_list:
            unique = True
            for filtered_expl in filtered_expl_list:
                if expl.path == filtered_expl.path:
                    unique = False
            if unique:
                filtered_expl_list.append(expl)
        self.console.info("Removed {} duplicate exploits.".format(len(expl_list) - len(filtered_expl_list)))
        return filtered_expl_list

    # filter the given list of exploits by operating system
    def filter_by_os(self, os, multi, expl_list):
        if os == 'all':
            return expl_list
        if os == 'linux':
            if multi:
                return list(
                    filter(lambda expl: (expl.os == 'linux' or expl.os == 'unix' or expl.os == 'multi'), expl_list))
            return list(filter(lambda expl: (expl.os == 'linux' or expl.os == 'unix'), expl_list))
        elif os == 'windows':
            if multi:
                return list(filter(lambda expl: (expl.os == 'windows' or expl.os == 'multi'), expl_list))
            return list(filter(lambda expl: expl.os == 'windows', expl_list))
        else:
            return []

    # filter the given list of exploits by a minimum reliability ranking
    def filter_by_rank(self, rank, expl_list):
        return list(filter(lambda expl: self.compare_rankings(expl.rank, rank), expl_list))

    # compare a given rank of a exploit to the demanded minimum rank
    def compare_rankings(self, rank, min_rank):
        return Rank.value_from_str(rank) >= Rank.value_from_str(min_rank)

    # sort exploits by the msf module rank attribute
    def sort_exploits(self, expls):
        if len(expls) == 0:
            return []
        expls.sort(key=lambda x: Rank.value_from_str(x.rank), reverse=True)
        self.console.info("Sorted exploits.")
        return expls
Example #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