Пример #1
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
Пример #2
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)