Exemplo n.º 1
0
 def emit_messages(self, wp_report: Dict[str, Any]) -> None:
     """
     Sends the CEF syslog messages for the report.
     """
     log.debug(f"Sending Syslog messages for site {wp_report['site']}")
     for m in self.get_messages(wp_report):
         self.syslog.info(m)
Exemplo n.º 2
0
    def __init__(self, conf):
        # (Re)init logger with config
        init_log(verbose=conf['verbose'],
                 quiet=conf['quiet'],
                 logfile=conf['log_file'])

        self.delete_tmp_wpscan_files()

        # Init DB interface
        self.wp_reports = WPWatcherDataBase(conf['wp_reports'])

        # Dump config
        conf.update({'wp_reports': self.wp_reports.filepath})
        log.debug("WPWatcher configuration:{}".format(self.dump_config(conf)))

        # Init scanner
        self.scanner = WPWatcherScanner(conf)

        # Save sites
        self.wp_sites = conf['wp_sites']

        # Asynchronous executor
        self.executor = concurrent.futures.ThreadPoolExecutor(
            max_workers=conf['asynch_workers'])
        # List of conccurent futures
        self.futures = []

        # Register the signals to be caught ^C , SIGTERM (kill) , service restart , will trigger interrupt()
        signal.signal(signal.SIGINT, self.interrupt)
        signal.signal(signal.SIGTERM, self.interrupt)

        #new reports
        self.new_reports = []
Exemplo n.º 3
0
    def _wpscan(self, *args):
        # WPScan arguments
        cmd = self.wpscan_executable + list(args)
        # Log wpscan command without api token
        log.debug("Running WPScan command: %s" %
                  " ".join(safe_log_wpscan_args(cmd)))
        # Run wpscan
        process = subprocess.Popen(cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
        # Append process to current process list and launch
        self.processes.append(process)
        wpscan_output, stderr = process.communicate()
        self.processes.remove(process)
        try:
            wpscan_output = wpscan_output.decode("utf-8")
        except UnicodeDecodeError:
            wpscan_output = wpscan_output.decode("latin1")
        """
        # Error when wpscan failed, except exit code 5: means the target has at least one vulnerability.
        #   See https://github.com/wpscanteam/CMSScanner/blob/master/lib/cms_scanner/exit_code.rb
        if process.returncode in [0,5]:
            # WPScan comamnd success
            log.debug("WPScan raw output:\n"+wpscan_output)
        
        # Log error
        else : 
            err_string, full_err_string=self.get_full_err_string(cmd, process.returncode, wpscan_output, stderr)
            log.error(err_string)
            log.debug(full_err_string)
        """

        return (process.returncode, wpscan_output)
Exemplo n.º 4
0
    def get_messages(self, wp_report: Dict[str, Any]) -> List[str]:
        """
        Return a list of CEF formatted messages
        """
        from cefevent import CEFEvent

        messages = []
        for v in self.EVENTS.keys():
            # make sure items is a list, cast error string to list
            items = wp_report[v] if isinstance(wp_report[v],
                                               list) else [wp_report[v]]
            for msg_data in items:
                if msg_data:
                    log.debug(f"Message data: {msg_data}")
                    c = CEFEvent()
                    # WPWatcher related fields
                    c.set_prefix("deviceVendor", self.DEVICE_VENDOR)
                    c.set_prefix("deviceProduct", self.DEVICE_PRODUCT)
                    c.set_prefix("deviceVersion", self.DEVICE_VERSION)
                    # Message common fields
                    c.set_prefix("signatureId", self.EVENTS[v][0])
                    c.set_prefix("name", self.EVENTS[v][1])
                    c.set_prefix("severity", self.EVENTS[v][2])
                    # Message supp infos
                    c.set_field("message", msg_data[:1022])
                    c.set_field("sourceHostName", wp_report["site"][:1022])
                    msg = c.build_cef()
                    log.debug(f"Message CEF: {msg}")
                    messages.append(msg)
        return messages
Exemplo n.º 5
0
 def get_messages(self, wp_report):
     """
     Return a list of CEF formatted messages
     """
     from cefevent import CEFEvent
     messages = []
     for v in self.EVENTS.keys():
         # make sure items is a list, cast error string to list
         items = wp_report[v] if isinstance(wp_report[v],
                                            list) else [wp_report[v]]
         for msg_data in items:
             if msg_data:
                 log.debug("Message data: {}".format(msg_data))
                 c = CEFEvent()
                 # WPWatcher related fields
                 c.set_prefix('deviceVendor', self.DEVICE_VENDOR)
                 c.set_prefix('deviceProduct', self.DEVICE_PRODUCT)
                 c.set_prefix('deviceVersion', VERSION)
                 # Message common fields
                 c.set_prefix('signatureId', self.EVENTS[v][0])
                 c.set_prefix('name', self.EVENTS[v][1])
                 c.set_prefix('severity', self.EVENTS[v][2])
                 # Message supp infos
                 c.set_field('message', msg_data[:1022])
                 c.set_field("sourceHostName", wp_report['site'][:1022])
                 msg = c.build_cef()
                 log.debug("Message CEF: {}".format(msg))
                 messages.append(msg)
     return messages
Exemplo n.º 6
0
 def emit_messages(self, wp_report):
     """
     Sends the CEF syslog messages for the report.
     """
     log.debug("Sending Syslog messages for site {}".format(
         wp_report["site"]))
     for m in self.get_messages(wp_report):
         self.syslog.info(m)
Exemplo n.º 7
0
    def _scan_site(self, wp_site: Site,
                   wp_report: ScanReport) -> Optional[ScanReport]:
        """
        Handled WPScan scanning , parsing, errors and reporting.
        Returns filled wp_report, None if interrupted or killed.
        Can raise `RuntimeError` if any errors.
        """

        # WPScan arguments
        wpscan_arguments = (self.wpscan_args + wp_site["wpscan_args"] +
                            ["--url", wp_site["url"]])

        # Output
        log.info(f"Scanning site {wp_site['url']}")

        # Launch WPScan
        wpscan_process = self.wpscan.wpscan(*wpscan_arguments)
        wp_report["wpscan_output"] = wpscan_process.stdout

        log.debug(f"WPScan raw output:\n{wp_report['wpscan_output']}")
        log.debug("Parsing WPScan output")

        try:
            # Use wpscan_out_parse module
            try:
                parser = WPScanJsonParser(
                    json.loads(wp_report["wpscan_output"]),
                    self.false_positive_strings +
                    wp_site["false_positive_strings"] + [
                        "No WPVulnDB API Token given",
                        "No WPScan API Token given"
                    ])
            except ValueError as err:
                parser = WPScanCliParser(
                    wp_report["wpscan_output"], self.false_positive_strings +
                    wp_site["false_positive_strings"] + [
                        "No WPVulnDB API Token given",
                        "No WPScan API Token given"
                    ])
            finally:
                wp_report.load_parser(parser)

        except Exception as err:
            raise RuntimeError(
                f"Could not parse WPScan output for site {wp_site['url']}\nOutput:\n{wp_report['wpscan_output']}"
            ) from err

        # Exit code 0: all ok. Exit code 5: Vulnerable. Other exit code are considered as errors
        if wpscan_process.returncode in [0, 5]:
            return wp_report

        # Quick return if interrupting and/or if user cancelled scans
        if self.interrupting or wpscan_process.returncode in [2, -2, -9]:
            return None

        # Other errors codes : 127, etc, simply raise error
        err_str = f"WPScan failed with exit code {wpscan_process.returncode}. \nArguments: {safe_log_wpscan_args(wpscan_arguments)}. \nOutput: \n{remove_color(wp_report['wpscan_output'])}\nError: \n{wpscan_process.stderr}"
        raise RuntimeError(err_str)
Exemplo n.º 8
0
 def notify(self, wp_site, wp_report, last_wp_report):
     # Will print parsed readable Alerts, Warnings, etc as they will appear in email reports
     log.debug("%s\n" % (
         WPWatcherNotification.build_message(
             wp_report,
             warnings=self.send_warnings or self.
             send_infos,  # switches to include or not warnings and infos
             infos=self.send_infos)))
     if self.should_notify(wp_report, last_wp_report):
         self._notify(wp_site, wp_report, last_wp_report)
         return True
     else:
         return False
Exemplo n.º 9
0
    def wpscan(self, *args):
        (exit_code, output) = (0, "")
        # WPScan arguments
        cmd = shlex.split(self.path) + list(args)
        # Log wpscan command without api token
        log.debug("Running WPScan command: %s" %
                  ' '.join(safe_log_wpscan_args(cmd)))
        # Run wpscan -------------------------------------------------------------------
        try:
            process = subprocess.Popen(cmd,
                                       stdout=subprocess.PIPE,
                                       stderr=open(os.devnull, 'w'))
            # Append process to current process list and launch
            self.processes.append(process)
            wpscan_output, _ = process.communicate()
            self.processes.remove(process)
            try:
                wpscan_output = wpscan_output.decode("utf-8")
            except UnicodeDecodeError:
                wpscan_output = wpscan_output.decode("latin1")
            # Error when wpscan failed, except exit code 5: means the target has at least one vulnerability.
            #   See https://github.com/wpscanteam/CMSScanner/blob/master/lib/cms_scanner/exit_code.rb
            if process.returncode not in [0, 5]:
                # Handle error
                err_string = "WPScan command '%s' failed with exit code %s %s" % (
                    ' '.join(safe_log_wpscan_args(cmd)), str(
                        process.returncode), ". WPScan output: %s" %
                    wpscan_output if wpscan_output else '')
                log.error(oneline(err_string))
            else:
                # WPScan comamnd success
                log.debug("WPScan raw output:\n" + wpscan_output)
            (exit_code, output) = (process.returncode, wpscan_output)
        except (CalledProcessError) as err:
            # Handle error --------------------------------------------------
            wpscan_output = str(err.output)
            err_string = "WPScan command '%s' failed with exit code %s %s\nError:\n%s" % (
                ' '.join(safe_log_wpscan_args(cmd)), str(process.returncode),
                ". WPScan output: %s" % wpscan_output if wpscan_output else '',
                traceback.format_exc())

            log.error(oneline(err_string))
            (exit_code, output) = (err.returncode, wpscan_output)
        except FileNotFoundError as err:
            err_string = "Could not find wpscan executable. \nError:\n%s" % (
                traceback.format_exc())
            log.error(oneline(err_string))
            (exit_code, output) = (-1, "")
        return ((exit_code, output))
Exemplo n.º 10
0
 def open(self) -> None:
     """
     Acquire the file lock for the DB file. 
     """
     try:
         self._wp_report_file_lock.acquire(timeout=1)
     except Timeout as err:
         raise RuntimeError(f"Could not use the database file '{self.filepath}' because another instance of WPWatcher is using it. ") from err
     log.debug(f"Acquired DB lock file '{self.filepath}.lock'")
     try:
         self.write()
     except:
         log.error(
             f"Could not write wp_reports database: {self.filepath}. Use '--reports null' to ignore local Json database."
         )
         raise
Exemplo n.º 11
0
    def __init__(self, conf: Config):
        """
        Arguments:
        - `conf`: the configuration dict. Required
        """
        # (Re)init logger with config
        _init_log(verbose=conf["verbose"],
                  quiet=conf["quiet"],
                  logfile=conf["log_file"])

        self._delete_tmp_wpscan_files()

        # Init DB interface
        self.wp_reports: DataBase = DataBase(filepath=conf["wp_reports"],
                                             daemon=conf['daemon'])

        # Update config before passing it to WPWatcherScanner
        conf.update({"wp_reports": self.wp_reports.filepath})

        # Init scanner
        self.scanner: Scanner = Scanner(conf)

        # Save sites
        conf["wp_sites"] = [Site(site_conf) for site_conf in conf["wp_sites"]]
        self.wp_sites: List[Site] = conf["wp_sites"]

        # Asynchronous executor
        self._executor: concurrent.futures.ThreadPoolExecutor = (
            concurrent.futures.ThreadPoolExecutor(
                max_workers=conf["asynch_workers"]))

        # List of conccurent futures
        self._futures: List[concurrent.futures.Future] = [
        ]  # type: ignore [type-arg]

        # Register the signals to be caught ^C , SIGTERM (kill) , service restart , will trigger interrupt()
        signal.signal(signal.SIGINT, self.interrupt)
        signal.signal(signal.SIGTERM, self.interrupt)

        self.new_reports = ReportCollection()
        "New reports, cleared and filled when running `run_scans`."

        self.all_reports = ReportCollection()
        "All reports an instance of `WPWatcher` have generated using `run_scans`."

        # Dump config
        log.debug(f"Configuration:{repr(conf)}")
Exemplo n.º 12
0
    def _wpscan_site(self, wp_site, wp_report):
        # WPScan arguments
        wpscan_arguments = self.wpscan_args + wp_site['wpscan_args'] + [
            '--url', wp_site['url']
        ]
        # Output
        log.info("Scanning site %s" % wp_site['url'])
        # Launch WPScan
        (wpscan_exit_code,
         wp_report["wpscan_output"]) = self.wpscan.wpscan(*wpscan_arguments)

        # Exit code 0: all ok. Exit code 5: Vulnerable. Other exit code are considered as errors
        if wpscan_exit_code in [0, 5]:
            # Call parse_result from parser.py
            log.debug("Parsing WPScan output")
            try:
                wp_report['infos'], wp_report['warnings'], wp_report[
                    'alerts'] = parse_results(
                        wp_report['wpscan_output'],
                        self.false_positive_strings +
                        wp_site['false_positive_strings'] +
                        ['No WPVulnDB API Token given'])
                wp_report['errors'] = []  # clear errors if any
            except Exception as err:
                err_str = "Could not parse WPScan output for site %s\n%s" % (
                    wp_site['url'], traceback.format_exc())
                log.error(err_str)
                raise RuntimeError(err_str) from err
            else:
                return wp_report

        # Handle scan errors -----

        # Quick return if interrupting and/or if user cacelled scans
        if self.interrupting or wpscan_exit_code in [2, -2, -9]:
            return None

        # Other errors codes : -9, -2, 127, etc:
        # or wpscan_exit_code not in [1,3,4]
        # If WPScan error, add the error to the reports
        # This types if errors will be written into the Json database file exit codes 1,3,4
        err_str = "WPScan failed with exit code %s. \nWPScan arguments: %s. \nWPScan output: \n%s" % (
            (wpscan_exit_code, safe_log_wpscan_args(wpscan_arguments),
             wp_report['wpscan_output']))
        raise RuntimeError(err_str)
Exemplo n.º 13
0
    def _wpscan(
            self, *args: str
    ) -> subprocess.CompletedProcess:  # type: ignore [type-arg]
        # WPScan arguments
        arguments = list(args)
        if arguments[0] == 'wpscan':
            arguments.pop(0)
        cmd = self._wpscan_path + arguments
        # Log wpscan command without api token
        log.debug(
            f"Running WPScan command: {' '.join(safe_log_wpscan_args(cmd))}")
        # Run wpscan
        process = subprocess.Popen(cmd,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
        # Append process to current process list and launch
        self.processes.append(process)

        if self._scan_timeout:
            try:
                stdout, stderr = timeout(self._scan_timeout.total_seconds(),
                                         process.communicate)
            except TimeoutError as err:
                process.kill()
                # Raise error
                err_str = f"WPScan process '{safe_log_wpscan_args(cmd)}' timed out after {self._scan_timeout.total_seconds()} seconds. Setup 'scan_timeout' to allow more time. "
                raise RuntimeError(err_str) from err
        else:
            stdout, stderr = process.communicate()

        self.processes.remove(process)
        try:
            out_decoded = stdout.decode("utf-8")
            err_decoded = stderr.decode("utf-8")
        except UnicodeDecodeError:
            out_decoded = stdout.decode("latin1", errors="replace")
            err_decoded = stderr.decode("latin1", errors="replace")
        finally:
            return subprocess.CompletedProcess(args=cmd,
                                               returncode=process.returncode,
                                               stdout=out_decoded,
                                               stderr=err_decoded)
Exemplo n.º 14
0
    def _wpscan_site(self, wp_site, wp_report):
        '''Handled WPScan scanning , parsing, errors and reporting.  
        Returns filled wp_report, None if interrupted or killed.  
        Can raise RuntimeError if WPScan failed'''
        # WPScan arguments
        wpscan_arguments=self.wpscan_args+wp_site['wpscan_args']+['--url', wp_site['url']]
        # Output
        log.info("Scanning site %s"%wp_site['url'] )
        # Launch WPScan 
        (wpscan_exit_code, wp_report["wpscan_output"]) = self.wpscan.wpscan(*wpscan_arguments)

        log.debug("Parsing WPScan output")
        try:
            # Call parse_results_from_string from wpscan_out_parse module 
            results = parse_results_from_string(wp_report['wpscan_output'] ,
                self.false_positive_strings + wp_site['false_positive_strings'] + ['No WPVulnDB API Token given'] )

            wp_report['infos'], wp_report['warnings'] , wp_report['alerts'], wp_report['summary'] = results['infos'], results['warnings'], results['alerts'], results['summary']
            
            if results['error']:
                wp_report['error']+=results['error']

        except Exception as err:
            err_str="Could not parse WPScan output for site %s\n%s"%(wp_site['url'],traceback.format_exc())
            log.error(err_str)
            raise RuntimeError(err_str) from err

        # Exit code 0: all ok. Exit code 5: Vulnerable. Other exit code are considered as errors
        if wpscan_exit_code in [0,5]:
            return wp_report
        
        # Quick return if interrupting and/or if user cacelled scans
        if self.interrupting or wpscan_exit_code in [2, -2, -9]:
            return None

        # Other errors codes : 127, etc, simply raise error
        err_str="WPScan failed with exit code %s. \nArguments: %s. \nOutput: \n%s"%((wpscan_exit_code, 
            safe_log_wpscan_args(wpscan_arguments), 
            re.sub(r'(\x1b|\[[0-9][0-9]?m)','', wp_report['wpscan_output']) ))
        raise RuntimeError(err_str)
Exemplo n.º 15
0
    def _wpscan(self, *args):
        (exit_code, output) = (0, "")
        # WPScan arguments
        cmd = self.wpscan_executable + list(args)
        # Log wpscan command without api token
        log.debug("Running WPScan command: %s" %
                  ' '.join(safe_log_wpscan_args(cmd)))
        # Run wpscan -------------------------------------------------------------------
        try:
            process = subprocess.Popen(cmd,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            # Append process to current process list and launch
            self.processes.append(process)
            wpscan_output, stderr = process.communicate()
            self.processes.remove(process)
            try:
                wpscan_output = wpscan_output.decode("utf-8")
            except UnicodeDecodeError:
                wpscan_output = wpscan_output.decode("latin1")
            # Error when wpscan failed, except exit code 5: means the target has at least one vulnerability.
            #   See https://github.com/wpscanteam/CMSScanner/blob/master/lib/cms_scanner/exit_code.rb
            if process.returncode in [0, 5]:
                # WPScan comamnd success
                log.debug("WPScan raw output:\n" + wpscan_output)

            # Log error ----
            else:
                err_string, full_err_string = self.get_full_err_string(
                    cmd, process.returncode, wpscan_output, stderr)
                log.error(err_string)
                log.debug(full_err_string)

            return ((process.returncode, wpscan_output))

        except FileNotFoundError as err:
            err_string = "Could not find wpscan executable.\n%s" % (
                traceback.format_exc())
            log.error(oneline(err_string))
            raise RuntimeError(err_string) from err
Exemplo n.º 16
0
    def scan_site(self, wp_site, last_wp_report=None):

        # Init report variables
        wp_report = {
            "site": wp_site['url'],
            "status": None,
            "datetime": datetime.now().strftime(DATE_FORMAT),
            "last_email": None,
            "errors": [],
            "infos": [],
            "warnings": [],
            "alerts": [],
            "fixed": [],
            "wpscan_output": ""  # will be deleted
        }

        # Skip if the daemon mode is enabled and scan already happend in the last configured `daemon_loop_wait`
        if last_wp_report and self.skip_this_site(wp_report, last_wp_report):
            return None

        # Launch WPScan
        try:
            # Abnormal failure exit codes not in 0-5
            if not self.wpscan_site(wp_site, wp_report):
                log.error(
                    "Abnormal failure scanning %s exit codes not 0 or 5" %
                    (wp_site['url']))
                return None

        except RuntimeError as err:
            # Try to handle error and return, recall scan_site()
            wp_report_new, handled = self.handle_wpscan_err(wp_site, wp_report)
            if handled:
                wp_report.update(wp_report_new)
                return wp_report

            elif not self.interrupting:
                log.error("Could not scan site %s" % wp_site['url'])
                log.debug(traceback.format_exc())
                wp_report['errors'].append(str(err))
                # Fail fast
                self.check_fail_fast()

            else:
                return None

        self.fill_report_status(wp_report)

        # Prescan handling
        if self.prescan_without_api_token and not self.retreive_api_token(
                wp_site['wpscan_args']) and wp_report['status'] in [
                    'WARNING', 'ALERT'
                ]:
            self.prescanned_sites_warn.append(wp_site)
            log.warning(
                "Site %s triggered prescan warning, it will be scanned with API token at the end"
                % (wp_site['url']))
            return None

        self.log_report_results(wp_report)

        # Write wpscan output
        wpscan_results_file = self.write_wpscan_output(wp_report)
        if wpscan_results_file:
            log.info("WPScan output saved to file %s" % wpscan_results_file)

        # Updating report entry with data from last scan
        self.update_report(wp_report, last_wp_report, wp_site)

        # Notify recepients if match triggers
        try:
            if self.mail.notify(wp_site, wp_report, last_wp_report):
                # Store report time
                wp_report['last_email'] = datetime.now().strftime(DATE_FORMAT)
                # Discard fixed items because infos have been sent
                wp_report['fixed'] = []
        except RuntimeError:
            # Fail fast
            self.check_fail_fast()

        # Save scanned site
        self.scanned_sites.append(wp_site['url'])

        self.terminate_scan(wp_site, wp_report)

        return (wp_report)
Exemplo n.º 17
0
    def scan_site(self, wp_site):
        wp_site=self.format_site(wp_site)
        # Init report variables
        wp_report={
            "site":wp_site['url'],
            "status":None,
            "datetime": datetime.now().strftime('%Y-%m-%dT%H-%M-%S'),
            "last_email":None,
            "errors":[],
            "infos":[],
            "warnings":[],
            "alerts":[],
            "fixed":[],
            "wpscan_output":None # will be deleted
        }

        # Find last site result if any
        last_wp_report=[r for r in self.wp_reports if r['site']==wp_site['url']]
        if len(last_wp_report)>0: 
            last_wp_report=last_wp_report[0]
            # Skip if the daemon mode is enabled and scan already happend in the last configured `daemon_loop_wait`
            if ( self.conf['daemon'] and 
                datetime.strptime(wp_report['datetime'],'%Y-%m-%dT%H-%M-%S') - datetime.strptime(last_wp_report['datetime'],'%Y-%m-%dT%H-%M-%S') < self.conf['daemon_loop_sleep']):
                log.info("Daemon skipping site %s because already scanned in the last %s"%(wp_site['url'] , self.conf['daemon_loop_sleep']))
                self.scanned_sites.append(None)
                return None
        else: last_wp_report=None
        
        # WPScan arguments
        wpscan_arguments=self.conf['wpscan_args']+wp_site['wpscan_args']+['--url', wp_site['url']]

        # Output
        log.info("Scanning site %s"%wp_site['url'] )
        # Launch WPScan -------------------------------------------------------
        (wpscan_exit_code, wp_report["wpscan_output"]) = self.wpscan.wpscan(*wpscan_arguments)
        
        # Exit code 0: all ok. Exit code 5: Vulnerable. Other exit code are considered as errors
        # Handle scan errors
        if wpscan_exit_code not in [0,5]:
            # Quick return if interrupting
            if self.interrupting: return None
            
            # Quick return if user cacelled scans
            if wpscan_exit_code in [2]: return None

            # Fail fast
            if self.conf['fail_fast']:
                if not self.interrupting: 
                    log.error("Failure")
                    self.interrupt()
                else: return None # Interrupt will generate other errors

            # If WPScan error, add the error to the reports
            # This types if errors will be written into the Json database file
            if wpscan_exit_code in [1,3,4]:

                err_str="WPScan failed with exit code %s. \nWPScan arguments: %s. \nWPScan output: \n%s"%((wpscan_exit_code, safe_log_wpscan_args(wpscan_arguments), wp_report['wpscan_output']))
                wp_report['errors'].append(err_str)
                log.error("Could not scan site %s"%wp_site['url'])

                # Try to handle error and return
                wp_report, handled = self.handle_wpscan_err(wp_site, wp_report)
                if handled: return wp_report

            # Other errors codes : -9, -2, 127, etc: Just return None right away
            else: return None 
            
        # No errors with wpscan -----------------------------
        else:
            # Write wpscan output 
            wpscan_results_file=None
            if self.conf['wpscan_output_folder'] :
                wpscan_results_file=os.path.join(self.conf['wpscan_output_folder'],
                    get_valid_filename('WPScan_results_%s_%s.txt' % (wp_site['url'], wp_report['datetime'])))
                with open(wpscan_results_file, 'w') as wpout:
                    wpout.write(re.sub(r'(\x1b|\[[0-9][0-9]?m)','', str(wp_report['wpscan_output'])))
            
            log.debug("Parsing WPScan output")
            # Call parse_result from parser.py ------------------------
            wp_report['infos'], wp_report['warnings'] , wp_report['alerts']  = parse_results(wp_report['wpscan_output'] , 
                self.conf['false_positive_strings']+wp_site['false_positive_strings'] )
            
            # Updating report entry with data from last scan if any
            if last_wp_report:
                self.update_report(wp_report, last_wp_report)
            
            # Print WPScan findings ------------------------------------------------------
            for info in wp_report['infos']:
                log.info(oneline("** WPScan INFO %s ** %s" % (wp_site['url'], info )))
            for fix in wp_report['fixed']:
                log.info(oneline("** FIXED %s ** %s" % (wp_site['url'], fix )))
            for warning in wp_report['warnings']:
                log.warning(oneline("** WPScan WARNING %s ** %s" % (wp_site['url'], warning )))
            for alert in wp_report['alerts']:
                log.critical(oneline("** WPScan ALERT %s ** %s" % (wp_site['url'], alert )))

            if wpscan_results_file: log.info("WPScan results saved to file %s"%wpscan_results_file)
        
        # Report status ------------------------------------------------
        if len(wp_report['errors'])>0:wp_report['status']="ERROR"
        elif len(wp_report['warnings'])>0 and len(wp_report['alerts']) == 0: wp_report['status']='WARNING'
        elif len(wp_report['alerts'])>0: wp_report['status']='ALERT'
        elif len(wp_report['fixed'])>0: wp_report['status']='FIXED'
        else: wp_report['status']='INFO'

        # Will print parsed readable Alerts, Warnings, etc as they will appear in email reports
        log.debug("\n%s\n"%(build_message(wp_report, 
                warnings=self.conf['send_warnings'] or self.conf['send_infos'], # switches to include or not warnings and infos
                infos=self.conf['send_infos'])))

        # Notify recepients if match triggers and no errors
        self.notify(wp_site, wp_report, last_wp_report)
        # Save scanned site
        self.scanned_sites.append(wp_site['url'])
        # Discard wpscan_output from report
        del wp_report['wpscan_output']
        # Save report in global instance database and to file when a site has been scanned
        self.update_and_write_wp_reports([wp_report])
        # Print progress
        print_progress_bar(len(self.scanned_sites), len(self.conf['wp_sites'])) 
        return(wp_report)
Exemplo n.º 18
0
 def close(self) -> None:
     """
     Release the file lock.
     """
     self._wp_report_file_lock.release()
     log.debug(f"Released DB lock file '{self.filepath}.lock'")
Exemplo n.º 19
0
    def scan_site(self, wp_site, last_wp_report=None):
        '''Orchestrate the scanning of a site.  
        Return the final wp_report or None if something happened.
        '''

        # Init report variables
        wp_report={
            "site":wp_site['url'],
            "status":None,
            "datetime": datetime.now().strftime(DATE_FORMAT),
            "last_email":None,
            "error":'',
            "infos":[],
            "warnings":[],
            "alerts":[],
            "fixed":[],
            "summary":None,
            "wpscan_output":"" # will be deleted
        }

        # Skip if the daemon mode is enabled and scan already happend in the last configured `daemon_loop_wait`
        if last_wp_report and self.skip_this_site(wp_report, last_wp_report): 
            return None
        
        # Launch WPScan
        try:
            # If report is None, return None right away
            if not self.wpscan_site(wp_site, wp_report): 
                return None

        except RuntimeError as err:
            # Try to handle error and return, will recall scan_site()
            wp_report_new, handled = self.handle_wpscan_err(wp_site, wp_report)                
            if handled:
                wp_report=wp_report_new
                return wp_report

            elif not self.interrupting: 
                log.error("Could not scan site %s"%wp_site['url'])
                log.debug(traceback.format_exc())
                wp_report['error']+=str(err)
                # Fail fast
                self.check_fail_fast()

            else:
                return None

        self.fill_report_status(wp_report)

        self.log_report_results(wp_report)
        
        # Write wpscan output 
        wpscan_results_file=self.write_wpscan_output(wp_report)
        if wpscan_results_file: log.info("WPScan output saved to file %s"%wpscan_results_file)
        
        # Updating report entry with data from last scan 
        self.update_report(wp_report, last_wp_report, wp_site)

        # Notify recepients if match triggers
        try:
            # Will print parsed readable Alerts, Warnings, etc as they will appear in email reports
            log.debug("%s\n"%(WPWatcherNotification.build_message(wp_report, warnings=True, infos=True)))
            if self.mail.notify(wp_site, wp_report, last_wp_report):
                # Store report time
                wp_report['last_email']=datetime.now().strftime(DATE_FORMAT)
                # Discard fixed items because infos have been sent
                wp_report['fixed']=[]

        except RuntimeError: 
            # Fail fast
            self.check_fail_fast()

        self.fill_report_status(wp_report)

        # Discard wpscan_output from report
        if 'wpscan_output' in wp_report: 
            del wp_report['wpscan_output']

        # Send syslog if self.syslog is not None
        if self.syslog:
            try:
                self.syslog.emit_messages(wp_report)
            except Exception as err:
                log.error("Unable to send the syslog messages for site "+wp_site['url']+"\n"+traceback.format_exc())
                wp_report['error']+="Unable to send all syslog messages for site "+wp_site['url']+"\n"+traceback.format_exc()
                self.check_fail_fast()

        self.fill_report_status(wp_report)

        # Save scanned site
        self.scanned_sites.append(wp_site['url'])

        return(wp_report)