Example #1
0
    def _scan_site(self, wp_site: Site) -> Optional[ScanReport]:
        """
        Helper method to wrap the scanning process of `WPWatcherScanner.scan_site` and add the following:
        
        - Find the last report in the database and launch the scan
        - Write it in DB after scan.
        - Print progress bar

        This function can be called asynchronously.
        """

        last_wp_report = self.wp_reports.find(ScanReport(site=wp_site["url"]))

        # Launch scanner
        wp_report = self.scanner.scan_site(wp_site, last_wp_report)

        # Save report in global instance database and to file when a site has been scanned
        if wp_report:
            self.wp_reports.write([wp_report])
        else:
            log.info(f"No report saved for site {wp_site['url']}")

        # Print progress
        print_progress_bar(len(self.scanner.scanned_sites), len(self.wp_sites))
        return wp_report
Example #2
0
    def scan_site_wrapper(self, wp_site):
        """
        Helper method to wrap the raw scanning process that offer WPWatcherScanner.scan_site() and add the following:
        - Handle site structure formatting
        - Find the last report in the database and launch the scan
        - Write it in DB after scan.
        - Print progress bar
        This function will be called asynchronously.
        Return one report
        """

        wp_site = self.format_site(wp_site)
        last_wp_report = self.wp_reports.find_last_wp_report(
            {"site": wp_site["url"]})

        # Launch scanner
        wp_report = self.scanner.scan_site(wp_site, last_wp_report)
        # Save report in global instance database and to file when a site has been scanned
        if wp_report:
            self.wp_reports.update_and_write_wp_reports([wp_report])
        else:
            log.info("No report saved for site %s" % wp_site["url"])
        # Print progress
        print_progress_bar(len(self.scanner.scanned_sites), len(self.wp_sites))
        return wp_report
Example #3
0
    def scan_site_wrapper(self, wp_site, with_api_token=False):

        wp_site = self.format_site(wp_site)
        last_wp_report = self.wp_reports.find_last_wp_report(
            {'site': wp_site['url']})
        if with_api_token:
            wp_site['wpscan_args'].extend(
                ["--api-token", self.scanner.api_token])

        # Launch scanner
        wp_report = self.scanner.scan_site(wp_site, last_wp_report)
        # Save report in global instance database and to file when a site has been scanned
        if wp_report: self.wp_reports.update_and_write_wp_reports([wp_report])
        else: log.info("No report saved for site %s" % wp_site['url'])
        # Print progress
        print_progress_bar(len(self.scanner.scanned_sites), len(self.wp_sites))
        return (wp_report)
Example #4
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)