Beispiel #1
0
 def test_wpscan_output_folder(self):
     RESULTS_FOLDER="./results/"
     WPSCAN_OUTPUT_CONFIG = DEFAULT_CONFIG+"\nwpscan_output_folder=%s"%RESULTS_FOLDER
     scanner=WPWatcherScanner(WPWatcherConfig(string=WPSCAN_OUTPUT_CONFIG).build_config()[0])
     self.assertTrue(os.path.isdir(RESULTS_FOLDER),"WPscan results folder doesn't seem to have been init")
     for s in WP_SITES:
         report={
             "site": s['url'],
             "status": "WARNING",
             "datetime": "2020-04-08T16-05-16",
             "last_email": None,
             "error": '',
             "infos": [
                 "[+]","blablabla"],
             "warnings": [
                 "[+] WordPress version 5.2.2 identified (Insecure, released on 2019-06-18).\n| Found By: Emoji Settings (Passive Detection)\n",
                 "[!] No WPVulnDB API Token given, as a result vulnerability data has not been output.\n[!] You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/users/sign_up"
             ],
             "alerts": [],
             "fixed": [],
             "summary":None,
             "wpscan_output":"This is real%s"%(s)
         }
         f=scanner.write_wpscan_output(report)
         f1=os.path.join(RESULTS_FOLDER, 'warning/', get_valid_filename('WPScan_output_%s_%s.txt' % (s['url'], "2020-04-08T16-05-16")))
         self.assertEqual(f, f1, "Inconsistent WPScan output filenames")
         self.assertTrue(os.path.isfile(f1),"WPscan output file doesn't exist")
         with open(f1, 'r') as out:
             self.assertEqual(out.read(), "This is real%s"%(s))
     shutil.rmtree(RESULTS_FOLDER)
Beispiel #2
0
 def write_wpscan_output(self, wp_report):
     '''Write WPScan output to configured place with `wpscan_output_folder` or return None
     '''
     # Subfolder
     folder="%s/"%wp_report['status'].lower()
     # Write wpscan output 
     wpscan_results_file=None
     if self.wpscan_output_folder :
         wpscan_results_file=os.path.join(self.wpscan_output_folder, folder , 
             get_valid_filename('WPScan_output_%s_%s.txt' % (wp_report['site'], wp_report['datetime'])))
         with open(wpscan_results_file, 'wb') as wpout:
             self._write_wpscan_output(wp_report, wpout)
     return(wpscan_results_file)
Beispiel #3
0
 def write_wpscan_output(self, wp_report):
     # Subfolder
     folder = "%s/" % wp_report['status'].lower(
     ) if wp_report['status'] != 'FIXED' else 'info/'
     # Write wpscan output
     wpscan_results_file = None
     if self.wpscan_output_folder:
         wpscan_results_file = os.path.join(
             self.wpscan_output_folder, folder,
             get_valid_filename('WPScan_output_%s_%s.txt' %
                                (wp_report['site'], 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'])))
     return (wpscan_results_file)
Beispiel #4
0
    def send_report(
        self, wp_report, email_to
    ):  #, send_infos=False, send_warnings=True, send_errors=False, attach_wpscan_output=False):

        # Building message
        message = MIMEMultipart("html")
        message['Subject'] = 'WPWatcher %s report - %s - %s' % (
            wp_report['status'], wp_report['site'], wp_report['datetime'])
        message['From'] = self.from_email
        message['To'] = email_to

        # Email body
        body = self.build_message(
            wp_report,
            warnings=self.send_warnings or
            self.send_infos,  # switches to include or not warnings and infos
            infos=self.send_infos)

        message.attach(MIMEText(body))

        # Attachment log if attach_wpscan_output
        if self.attach_wpscan_output:
            # Remove color
            wp_report['wpscan_output'] = re.sub(
                r'(\x1b|\[[0-9][0-9]?m)', '', str(wp_report['wpscan_output']))
            # Read the WPSCan output
            attachment = io.BytesIO(wp_report['wpscan_output'].encode())
            part = MIMEApplication(attachment.read(), Name='WPScan_output')
            # Sanitize WPScan report filename
            wpscan_report_filename = get_valid_filename(
                'WPScan_output_%s_%s' %
                (wp_report['site'], wp_report['datetime']))
            # Add header as key/value pair to attachment part
            part.add_header(
                "Content-Disposition",
                "attachment; filename=%s.txt" % (wpscan_report_filename),
            )
            # Attach the report
            message.attach(part)
            log.info("%s attached" % (wpscan_report_filename))
        else:
            log.info(
                "No file attached, set attach_wpscan_output=Yes or use --attach to attach WPScan output to emails"
            )
        # # Connecting and sending
        self.send_mail(message, email_to)
        log.info("Email sent: %s to %s" % (message['Subject'], email_to))
Beispiel #5
0
    def send_report(self, wp_report, email_to):
        '''Build MIME message based on report and call send_mail'''

        # Building message
        message = MIMEMultipart("html")
        message['Subject'] = 'WPWatcher %s report - %s - %s' % (
            wp_report['status'], wp_report['site'], wp_report['datetime'])
        message['From'] = self.from_email
        message['To'] = ','.join(email_to)

        # Email body
        body = self.build_message(wp_report,
                                  warnings=self.send_warnings,
                                  infos=self.send_infos)
        if self.use_monospace_font:
            body = '<font face="Courier New, Courier, monospace" size="-1">' + body + '</font>'

        message.attach(MIMEText(body, 'html'))

        # Attachment log if attach_wpscan_output
        if self.attach_wpscan_output:
            # Remove color
            wp_report['wpscan_output'] = re.sub(
                r'(\x1b|\[[0-9][0-9]?m)', '', str(wp_report['wpscan_output']))
            # Read the WPSCan output
            attachment = io.BytesIO(wp_report['wpscan_output'].encode())
            part = MIMEApplication(attachment.read(), Name='WPScan_output')
            # Sanitize WPScan report filename
            wpscan_report_filename = get_valid_filename(
                'WPScan_output_%s_%s' %
                (wp_report['site'], wp_report['datetime']))
            # Add header as key/value pair to attachment part
            part.add_header(
                "Content-Disposition",
                "attachment; filename=%s.txt" % (wpscan_report_filename),
            )
            # Attach the report
            message.attach(part)
            log.info("%s attached" % (wpscan_report_filename))
        else:
            log.info(
                "No file attached, set attach_wpscan_output=Yes or use --attach to attach WPScan output to emails"
            )
        # # Connecting and sending
        self.send_mail(message, email_to)
        log.info("Email sent: %s to %s" % (message['Subject'], email_to))
Beispiel #6
0
 def write_wpscan_output(self, wp_report: ScanReport) -> Optional[str]:
     """Write WPScan output to configured place with `wpscan_output_folder` if configured"""
     # Subfolder
     folder = f"{wp_report['status'].lower()}/"
     # Write wpscan output
     if self.wpscan_output_folder:
         wpscan_results_file = os.path.join(
             self.wpscan_output_folder,
             folder,
             get_valid_filename(
                 f"WPScan_output_{wp_report['site']}_{wp_report['datetime']}.txt"
             ),
         )
         log.info(f"Saving WPScan output to file {wpscan_results_file}")
         with open(wpscan_results_file, "wb") as wpout:
             self._write_wpscan_output(wp_report, wpout)
             return wpout.name
     else:
         return None
Beispiel #7
0
    def _send_report(self, wp_report: Dict[str, Any], email_to: List[str],
                     wpscan_command: str) -> None:
        """Build MIME message based on report and call send_mail"""

        # Building message
        message = MIMEMultipart("html")
        message[
            "Subject"] = f"WPWatcher {wp_report['status']} report - {wp_report['site']} - {wp_report['datetime']}"
        message["From"] = self.from_email
        message["To"] = ",".join(email_to)

        # Email body
        body = self.build_message(wp_report, wpscan_command)
        if self.use_monospace_font:
            body = (
                f'<font face="Courier New, Courier, monospace" size="-1">{body}</font>'
            )

        message.attach(MIMEText(body, "html"))

        # Attachment log if attach_wpscan_output
        if self.attach_wpscan_output:
            # Remove color
            wp_report["wpscan_output"] = re.sub(
                r"(\x1b|\[[0-9][0-9]?m)", "", str(wp_report["wpscan_output"]))
            # Read the WPSCan output
            attachment = io.BytesIO(wp_report["wpscan_output"].encode())
            part = MIMEApplication(attachment.read(), Name="WPScan_output")
            # Sanitize WPScan report filename
            wpscan_report_filename = get_valid_filename(
                f"WPScan_output_{wp_report['site']}_{wp_report['datetime']}")
            # Add header as key/value pair to attachment part
            part.add_header(
                "Content-Disposition",
                f"attachment; filename={wpscan_report_filename}.txt",
            )
            # Attach the report
            message.attach(part)

        # Connecting and sending
        self._send_mail(message, email_to)
        log.info(f"Email sent: {message['Subject']} to {email_to}")
Beispiel #8
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)
Beispiel #9
0
    def send_report(self, wp_site, wp_report):
        # To
        if len(self.conf['email_errors_to'])>0 and wp_report['status']=='ERROR':
            to_email = ','.join( self.conf['email_errors_to'] )
        else: 
            to_email = ','.join( wp_site['email_to'] + self.conf['email_to'] )
        
        if to_email != "":
           
            # Building message
            message = MIMEMultipart("html")
            message['Subject'] = 'WPWatcher %s report - %s - %s' % (  wp_report['status'], wp_site['url'], wp_report['datetime'])
            message['From'] = self.conf['from_email']
            message['To'] = to_email

            # Email body
            body=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'])

            message.attach(MIMEText(body))
            
            # Attachment log if attach_wpscan_output
            if self.conf['attach_wpscan_output']:
                # Remove color
                wp_report['wpscan_output'] = re.sub(r'(\x1b|\[[0-9][0-9]?m)','', str(wp_report['wpscan_output']))
                # Read the WPSCan output
                attachment=io.BytesIO(wp_report['wpscan_output'].encode())
                part = MIMEBase("application", "octet-stream")
                part.set_payload(attachment.read())
                # Encode file in ASCII characters to send by email    
                encoders.encode_base64(part)
                # Sanitize WPScan report filename 
                wpscan_report_filename=get_valid_filename('WPScan_results_%s_%s' % (wp_site['url'], wp_report['datetime']))
                # Add header as key/value pair to attachment part
                part.add_header(
                    "Content-Disposition",
                    "attachment; filename=%s.txt"%(wpscan_report_filename),
                )
                # Attach the report
                message.attach(part)

            # Connecting and sending
            # SMTP Connection
            s = smtplib.SMTP(self.conf['smtp_server'])
            s.ehlo()
            # SSL
            if self.conf['smtp_ssl']:
                s.starttls()
            # SMTP Auth
            if self.conf['smtp_auth']:
                s.login(self.conf['smtp_user'], self.conf['smtp_pass'])
            # Send Email
            s.sendmail(self.conf['from_email'], to_email, message.as_string())
            s.quit()
            # Store report time
            wp_report['last_email']=datetime.now().strftime('%Y-%m-%dT%H-%M-%S')
            # Discard fixed items because infos have been sent
            wp_report['fixed']=[]
            log.info("Email sent: %s to %s" % (message['Subject'], to_email))
        else:
            log.info("Not sending WPWatcher %s email report because no email is configured for site %s"%(wp_report['status'], wp_site['url']))