def __init__(self): """Main program entrypoint""" # Parse arguments args = self.parse_args() # Init logger with CLi arguments init_log(args.verbose, args.quiet) # If template conf , print and exit if args.template_conf: self.template_conf() # Print "banner" log.info( "WPWatcher - Automating WPscan to scan and report vulnerable Wordpress sites" ) # If version, print and exit if args.version: self.verion() # Init WPWatcher obhect and dump reports if args.wprs != False: self.wprs(args.wprs, args.daemon) # Read config configuration = self.build_config_cli(args) # If daemon lopping if configuration['daemon']: # Run 4 ever WPWatcherDaemon(configuration) else: # Run scans and quit # Create main object wpwatcher = WPWatcher(configuration) exit_code, _ = wpwatcher.run_scans_and_notify() exit(exit_code)
def __init__(self, conf): log.info("Daemon mode selected, looping for ever...") # keep data in memory wpwatcher = WPWatcher(conf) while True: # Run scans for ever wpwatcher.run_scans_and_notify() log.info("Daemon sleeping %s and scanning again..." % conf['daemon_loop_sleep']) time.sleep(conf['daemon_loop_sleep'].total_seconds())
def main(_args: Optional[Sequence[Text]] = None) -> None: """Main program entrypoint""" # Parse arguments args: argparse.Namespace = get_arg_parser().parse_args(_args) # Init logger with CLi arguments _init_log(args.verbose, args.quiet) # If template conf , print and exit if args.template_conf: template_conf() # Print "banner" log.info( "WPWatcher - Automating WPscan to scan and report vulnerable Wordpress sites" ) if args.version: # Print and exit version() if args.wprs != False: # Init WPWatcherDataBase object and dump reports wprs(filepath=args.wprs, daemon=args.daemon) # Read config configuration = Config.fromcliargs(args) if args.show: # Init WPWatcherDataBase object and dump cli formatted report show( urlpart=args.show, filepath=configuration["wp_reports"], daemon=args.daemon, ) # Launch syslog test if args.syslog_test: syslog_test(configuration) # If daemon lopping if configuration["daemon"]: # Run 4 ever daemon = Daemon(configuration) daemon.loop() else: # Run scans and quit wpwatcher = WPWatcher(configuration) exit_code, reports = wpwatcher.run_scans() exit(exit_code)
def __init__(self): """Main program entrypoint""" # Parse arguments args = self.parse_args() # Init logger with CLi arguments init_log(args.verbose, args.quiet) # If template conf , print and exit if args.template_conf: self.template_conf() # Print "banner" log.info( "WPWatcher - Automating WPscan to scan and report vulnerable Wordpress sites" ) if args.version: # Print and exit self.verion() if args.wprs != False: # Init WPWatcherDataBase object and dump reports self.wprs(filepath=args.wprs, daemon=args.daemon) # Read config configuration = self.build_config_cli(args) if args.show: # Init WPWatcherDataBase object and dump cli formatted report self.show( urlpart=args.show, filepath=configuration["wp_reports"], daemon=args.daemon, ) # Launch syslog test if args.syslog_test: self.syslog_test(configuration) # If daemon lopping if configuration["daemon"]: # Run 4 ever WPWatcherDaemon(configuration) else: # Run scans and quit # Create main object wpwatcher = WPWatcher(configuration) exit_code, _ = wpwatcher.run_scans_and_notify() exit(exit_code)
def wprs(filepath=None, daemon=False): """Generate JSON file database summary""" db = WPWatcherDataBase(filepath, daemon=daemon) sys.stdout.buffer.write( WPWatcher.results_summary(db._data).encode("utf8")) sys.stdout.flush() exit(0)
def test_should_notify(self): # test send_errors, send_infos, send_warnings, resend_emails_after, email_errors_to # Init WPWatcher CONFIG=DEFAULT_CONFIG+"\nsend_infos=Yes\nsend_errors=Yes\nsend_warnings=No" wpwatcher = WPWatcher(WPWatcherConfig(string=CONFIG).build_config()[0]) # wpwatcher.scanner.mail # TODO
def test_send_report(self): # Init WPWatcher wpwatcher = WPWatcher(WPWatcherConfig(string=DEFAULT_CONFIG+"\nattach_wpscan_output=Yes").build_config()[0]) print(wpwatcher.__dict__) print(wpwatcher.scanner.__dict__) print(wpwatcher.scanner.mail.__dict__) # Send mail 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": ["This issue was fixed"], "summary":None, "wpscan_output":"This is real%s"%(s) } # notif=WPWatcherNotification(WPWatcherConfig(string=DEFAULT_CONFIG+"\nattach_wpscan_output=Yes").build_config()[0]) wpwatcher.scanner.mail.send_report(report, email_to='test', wpscan_command= 'just testing')
def test_scan_localhost_error_not_wordpress(self): # test info, warnings and alerts scanner = WPWatcherScanner( WPWatcherConfig(string=DEFAULT_CONFIG).build_config()[0]) report = scanner.scan_site( WPWatcher.format_site({'url': 'http://localhost:8080'})) self.assertEqual(report['status'], 'ERROR') self.assertRegex(report['error'], 'does not seem to be running WordPress')
def test_config(self): config=""" [wpwatcher] wpscan_args=[ "--format", "cli", "--no-banner", "--random-user-agent", "--disable-tls-checks" ] wp_sites=%s send_email_report=Yes send_infos=Yes send_errors=Yes send_warnings=No attach_wpscan_output=Yes resend_emails_after=5d wp_reports=./test.json follow_redirect=Yes """%(json.dumps(self.get_sites())) w=WPWatcher(WPWatcherConfig(string=config).build_config()[0]) exit_code, results=w.run_scans_and_notify() self.assertEqual(0, exit_code)
def __init__(self): args = self.parse_args() init_log(args.verbose, args.quiet) # If template conf , print and exit if args.template_conf: print(WPWatcherConfig.TEMPLATE_FILE) exit(0) log.info( "WPWatcher - Automating WPscan to scan and report vulnerable Wordpress sites" ) # If version, print and exit if args.version: log.info("Version:\t\t%s" % VERSION) log.info("Authors:\t\t%s" "" % AUTHORS) exit(0) # Init WPWatcher obhect and dump reports if args.wprs != False: if args.wprs == None: f = WPWatcher(WPWatcherConfig().build_config() [0]).find_wp_reports_file() else: f = args.wprs log.info("Reports: %s" % (f)) with open(f) as r: results = json.load(r) print(results_summary(results)) exit(0) # Read config configuration = self.build_config_cli(args) # Create main object wpwatcher = WPWatcher(configuration) # If daemon lopping if wpwatcher.conf['daemon']: log.info("Daemon mode selected, looping for ever...") results = None # Keep databse in memory while True: # Run scans for ever exit_code, results = wpwatcher.run_scans_and_notify() timesleep = wpwatcher.conf['daemon_loop_sleep'] log.info("Daemon sleeping %s and scanning again..." % timesleep) time.sleep(timesleep.total_seconds()) wpwatcher = WPWatcher(self.build_config_cli(args)) wpwatcher.wp_reports = results # Run scans and quit else: exit_code, results = wpwatcher.run_scans_and_notify() exit(exit_code)
def test_init_wpwatcher(self): # Init deafult watcher wpwatcher = WPWatcher(Config.fromstring(DEFAULT_CONFIG)) self.assertEqual(type(wpwatcher.scanner), Scanner, "Scanner doesn't seem to have been initialized") self.assertEqual(type(wpwatcher.scanner.mail), EmailSender, "EmailSender doesn't seem to have been initialized") self.assertEqual( type(wpwatcher.scanner.wpscan), WPScanWrapper, "WPScanWrapper doesn't seem to have been initialized") self.assertEqual( shlex.split(Config.fromstring(DEFAULT_CONFIG)['wpscan_path']), wpwatcher.scanner.wpscan._wpscan_path, "WPScan path seems to be wrong")
def test_init_wpwatcher(self): # Init deafult watcher wpwatcher = WPWatcher( WPWatcherConfig(string=DEFAULT_CONFIG).build_config()[0]) flag = WPWatcherConfig(string=DEFAULT_CONFIG).build_config()[0] self.assertEqual( type(wpwatcher.scanner), WPWatcherScanner, "WPWatcherScanner doesn't seem to have been initialized") self.assertEqual( type(wpwatcher.scanner.mail), WPWatcherNotification, "WPWatcherNotification doesn't seem to have been initialized") self.assertEqual( type(wpwatcher.scanner.wpscan), WPScanWrapper, "WPScanWrapper doesn't seem to have been initialized") self.assertEqual( shlex.split( WPWatcherConfig( string=DEFAULT_CONFIG).build_config()[0]['wpscan_path']), wpwatcher.scanner.wpscan.wpscan_executable, "WPScan path seems to be wrong")
def test_wpscan_output_folder(self): RESULTS_FOLDER = "./results/" WPSCAN_OUTPUT_CONFIG = DEFAULT_CONFIG + "\nwpscan_output_folder=%s" % RESULTS_FOLDER wpwatcher = WPWatcher( 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, "errors": [], "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": [], "wpscan_output": "This is real%s" % (s) } f = wpwatcher.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)
def show(urlpart, filepath=None, daemon=False): """Inspect a report in database""" db = WPWatcherDataBase(filepath, daemon=daemon) matching_reports = [r for r in db._data if urlpart in r["site"]] eq_reports = [r for r in db._data if urlpart == r["site"]] if len(eq_reports): sys.stdout.buffer.write( format_results(eq_reports[0], format="cli").encode("utf8")) elif len(matching_reports) == 1: sys.stdout.buffer.write( format_results(matching_reports[0], format="cli").encode("utf8")) elif len(matching_reports) > 1: sys.stdout.buffer.write( "The following sites match your search: \n".encode("utf8")) sys.stdout.buffer.write( WPWatcher.results_summary(matching_reports).encode("utf8")) sys.stdout.buffer.write( "\nPlease be more specific. \n".encode("utf8")) else: sys.stdout.buffer.write("No report found".encode("utf8")) exit(1) exit(0)
def test_should_notify(self): # test send_errors, send_infos, send_warnings, resend_emails_after, email_errors_to # Init WPWatcher CONFIG = DEFAULT_CONFIG + "\nsend_infos=Yes\nsend_errors=Yes\nsend_warnings=No" wpwatcher = WPWatcher(Config.fromstring(CONFIG))
def test_scan_radom_sites(self): # This test might be illegal in your country # Get list of Wordpress sites if not already downloaded filename='/tmp/wp_sites' if not os.path.isfile(filename): myfile = requests.get(SOURCE) open(filename, 'wb').write(myfile.content) # Select X from the 50M idxs = random.sample(range(50000), HOW_MANY) urls=[linecache.getline(filename, i) for i in idxs] # Prepare scan config CONFIG1=""" [wpwatcher] wp_sites=%s smtp_server=localhost:1025 [email protected] email_to=["*****@*****.**"] wpscan_args=["--rua", "--stealthy", "--format", "cli", "--no-banner", "--disable-tls-checks"] false_positive_strings=["You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/users/sign_up"] send_email_report=Yes log_file=./TEST-wpwatcher.log.conf wp_reports=./TEST-wp_reports.json.conf asynch_workers=10 follow_redirect=Yes wpscan_output_folder=./TEST-wpscan-results/ send_infos=Yes """%json.dumps([{'url':s.strip()} for s in urls]) # Select X from the 50M idxs = random.sample(range(50000), HOW_MANY) urls=[linecache.getline(filename, i) for i in idxs] # Prepare scan config CONFIG2=""" [wpwatcher] wp_sites=%s smtp_server=localhost:1025 [email protected] email_to=["*****@*****.**"] wpscan_args=["--rua", "--stealthy", "--format", "json", "--no-banner", "--disable-tls-checks"] false_positive_strings=["You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/users/sign_up"] send_email_report=Yes log_file=./TEST-wpwatcher.log.conf wp_reports=./TEST-wp_reports.json.conf asynch_workers=10 follow_redirect=Yes wpscan_output_folder=./TEST-wpscan-results/ attach_wpscan_output=Yes send_infos=Yes send_errors=Yes email_errors_to=["admins@domain"] # prescan_without_api_token=Yes """%json.dumps([{'url':s.strip()} for s in urls]) # Select X from the 50M idxs = random.sample(range(50000), HOW_MANY) urls=[linecache.getline(filename, i) for i in idxs] # Prepare scan config CONFIG3=""" [wpwatcher] wp_sites=%s smtp_server=localhost:1025 [email protected] email_to=["*****@*****.**"] wpscan_args=["--rua", "--stealthy", "--format", "json", "--no-banner", "--disable-tls-checks"] false_positive_strings=["You can get a free API token with 50 daily requests by registering at https://wpvulndb.com/users/sign_up"] send_email_report=Yes log_file=./TEST-wpwatcher.log.conf wp_reports=./TEST-wp_reports.json.conf asynch_workers=10 follow_redirect=Yes wpscan_output_folder=./TEST-wpscan-results/ attach_wpscan_output=Yes send_warnings=No send_errors=Yes fail_fast=Yes """%json.dumps([{'url':s.strip()} for s in urls]) # Launch SMPT debbug server smtpd.DebuggingServer(('localhost',1025), None ) executor = concurrent.futures.ThreadPoolExecutor(1) executor.submit(asyncore.loop) # Init WPWatcher w1 = WPWatcher(WPWatcherConfig(string=CONFIG1).build_config()[0]) # Run scans res1=w1.run_scans_and_notify() # Init WPWatcher w2 = WPWatcher(WPWatcherConfig(string=CONFIG2).build_config()[0]) # Run scans res2=w2.run_scans_and_notify() # Init WPWatcher w3 = WPWatcher(WPWatcherConfig(string=CONFIG3).build_config()[0]) # Run scans res3=w3.run_scans_and_notify() # Close mail server asyncore.close_all() self.assertEqual(type(res1), tuple, "run_scans_and_notify returned an invalied result")
def test_interrupt(self): wpwatcher = WPWatcher(Config.fromstring(DEFAULT_CONFIG)) with self.assertRaises(SystemExit): wpwatcher.interrupt()
def test_update_report(self): # Init WPWatcher wpwatcher = WPWatcher( WPWatcherConfig(string=DEFAULT_CONFIG).build_config()[0]) for s in WP_SITES: old = { "site": s['url'], "status": "WARNING", "datetime": "2020-04-08T16-05-16", "last_email": "2020-04-08T16-05-17", "errors": [], "infos": ["[+]", "blablabla"], "warnings": [ "[+] WordPress version 5.2.2 identified (Insecure, released on 2019-06-18).\nblablabla\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": ["This issue was fixed"], "wpscan_output": "" } new = { "site": s['url'], "status": "", "datetime": "2020-04-10T16-00-00", "last_email": None, "errors": [], "infos": ["[+]", "blablabla"], "warnings": [ "[!] 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": [], "wpscan_output": "" } expected = { "site": s['url'], "status": "", "datetime": "2020-04-10T16-00-00", "last_email": "2020-04-08T16-05-17", "errors": [], "infos": ["[+]", "blablabla"], "warnings": [ "[!] 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": [ "This issue was fixed", 'Issue regarding component "%s" has been fixed since last report.\nLast report sent the %s.\nFix detected the %s\nIssue details:\n[+] WordPress version 5.2.2 identified (Insecure, released on 2019-06-18).\nblablabla\n' % ("[+] WordPress version 5.2.2 identified (Insecure, released on 2019-06-18).", old['last_email'], new['datetime']) ], "wpscan_output": "" } wpwatcher.scanner.update_report(new, old, s) print(new) print(expected) self.assertEqual( new, expected, "There is an issue with fixed issues feature: the expected report do not match the report returned by update_report()" )
def test_wp_reports(self): SPECIFIC_WP_REPORTS_FILE_CONFIG = DEFAULT_CONFIG + "\nwp_reports=%s" # Compare with config and no config wpwatcher = WPWatcher( WPWatcherConfig(string=DEFAULT_CONFIG).build_config()[0]) paths_found = wpwatcher.wp_reports.find_wp_reports_file() wpwatcher2 = WPWatcher( WPWatcherConfig(string=SPECIFIC_WP_REPORTS_FILE_CONFIG % (paths_found)).build_config()[0]) self.assertEqual( wpwatcher.wp_reports._data, wpwatcher2.wp_reports._data, "WP reports database are different even if files are the same") # Test Reports database reports = [{ "site": "exemple.com", "status": "WARNING", "datetime": "2020-04-08T16-05-16", "last_email": None, "errors": [], "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": [] }, { "site": "exemple2.com", "status": "INFO", "datetime": "2020-04-08T16-05-16", "last_email": None, "errors": [], "infos": ["[+]", "blablabla"], "warnings": [], "alerts": [], "fixed": [] }] wpwatcher = WPWatcher( WPWatcherConfig(string=DEFAULT_CONFIG).build_config()[0]) wpwatcher.wp_reports.update_and_write_wp_reports(reports) # Test update for r in reports: self.assertIn( r, wpwatcher.wp_reports._data, "The report do not seem to have been saved into WPWatcher.wp_report list" ) # Test write method wrote_db = wpwatcher.wp_reports.build_wp_reports( wpwatcher.wp_reports.filepath) with open(wpwatcher.wp_reports.filepath, 'r') as db: wrote_db_alt = json.load(db) for r in reports: self.assertIn( r, wrote_db, "The report do not seem to have been saved into db file") self.assertIn( r, wrote_db_alt, "The report do not seem to have been saved into db file") self.assertEqual( wpwatcher.wp_reports._data, wrote_db_alt, "The database file wrote differ from in memory database") self.assertEqual( wpwatcher.wp_reports._data, wrote_db, "The database file wrote differ from in memory database")
from wpwatcher.core import WPWatcher from wpwatcher.config import WPWatcherConfig from wpwatcher.utils import get_valid_filename from wpwatcher.parser import parse_results from wpwatcher.notification import WPWatcherNotification from wpwatcher.scan import WPWatcherScanner import random import linecache # Constants NUMBER_OF_CONFIG_VALUES = 31 # Read URLS file # URLS="./wpwatcher-test-sites.txt.conf" WP_SITES = [ WPWatcher.format_site(s) for s in [{ "url": "exemple.com" }, { "url": "exemple2.com" }] ] # with open(URLS, 'r') as f: [ WP_SITES.append({'url':url.strip()}) for url in f.readlines() ] DEFAULT_CONFIG = """ [wpwatcher] wp_sites=%s smtp_server=localhost:1025 [email protected] email_to=["*****@*****.**"] """ % json.dumps(WP_SITES)
def test_interrupt(self): wpwatcher = WPWatcher( WPWatcherConfig(string=DEFAULT_CONFIG).build_config()[0]) with self.assertRaises(SystemExit): wpwatcher.interrupt()