Exemple #1
0
    def test_init_config_from_string(self):

        # Test minimal config
        config_dict = Config.fromstring(DEFAULT_CONFIG)
        self.assertEqual(config_dict['email_to'], ["*****@*****.**"])
        self.assertEqual(config_dict['from_email'],
                         "*****@*****.**")
        self.assertEqual(config_dict['smtp_server'], "localhost:1025")

        # Test config template file
        config_dict2 = Config.fromstring(Config.TEMPLATE_FILE)
        self.assertEqual(config_dict2['smtp_server'], "mailserver.de:587")
Exemple #2
0
    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")
Exemple #3
0
 def test_scan_localhost_error_not_wordpress(self):
     # test info, warnings and alerts
     scanner = Scanner(Config.fromstring(DEFAULT_CONFIG))
     report = scanner.scan_site(Site({'url': 'http://localhost:8080'}))
     self.assertEqual(report['status'], 'ERROR')
     self.assertRegex(report['error'],
                      'does not seem to be running WordPress')
Exemple #4
0
 def test_wpscan_output_folder(self):
     RESULTS_FOLDER="./results/"
     WPSCAN_OUTPUT_CONFIG = DEFAULT_CONFIG+"\nwpscan_output_folder=%s"%RESULTS_FOLDER
     scanner=Scanner(Config.fromstring(WPSCAN_OUTPUT_CONFIG))
     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)
Exemple #5
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)}")
Exemple #6
0
    def test_daemon(self):
        # test daemon_loop_sleep and daemon mode

        conf = Config.fromstring(DEFAULT_CONFIG)
        conf['asynch_workers'] += 1
        daemon = Daemon(conf)

        daemon.loop(ttl=timedelta(seconds=5))

        self.assertTrue(not any(
            [r.status() != 'ERROR' for r in daemon.wpwatcher.new_reports]))
        self.assertGreater(len(daemon.wpwatcher.new_reports), 1)
Exemple #7
0
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)
Exemple #8
0
    def test_init_config_from_file(self):

        # Test find config file, rename default file if already exist and restore after test
        paths_found = Config.find_config_files()
        existent_files = []
        if len(paths_found) == 0:
            paths_found = Config.find_config_files(create=True)
        else:
            existent_files = paths_found
            for p in paths_found:
                os.rename(p, '%s.temp' % p)
            paths_found = Config.find_config_files(create=True)

        # Init config and compare
        config_object = Config.fromenv()
        config_object2 = Config.fromfiles(paths_found)
        self.assertEqual(
            config_object, config_object2,
            "Config built with config path and without are different even if files are the same"
        )
        for f in paths_found:
            os.remove(f)
        for f in existent_files:
            os.rename('%s.temp' % f, f)
Exemple #9
0
    def test_build_config_cli(self):
        parser = get_arg_parser()

        tmp = tempfile.NamedTemporaryFile('w', delete=False)

        tmp.write("site10.com\nsite11.org\nsite12.fr")

        tmp.flush()

        args = parser.parse_args([
            '--url', 'site1.ca', 'site2.ca', '--urls', tmp.name, '--resend',
            '2m', '--loop', '60s', '--wpargs', '--format cli'
        ])

        wpwatcher_configuration = Config.fromcliargs(args)

        self.assertEqual(wpwatcher_configuration.get('wp_sites'),
                         [{
                             "url": "site1.ca"
                         }, {
                             "url": "site2.ca"
                         }, {
                             "url": "site10.com"
                         }, {
                             "url": "site11.org"
                         }, {
                             "url": "site12.fr"
                         }])

        self.assertIsInstance(wpwatcher_configuration.get('daemon_loop_sleep'),
                              timedelta)
        self.assertIsInstance(
            wpwatcher_configuration.get('resend_emails_after'), timedelta)

        self.assertEqual(wpwatcher_configuration.get('wpscan_args'), [
            "--random-user-agent", "--format", "json", "--cache-ttl", "0",
            "--format", "cli"
        ])

        os.remove(tmp.name)
Exemple #10
0
    def test_send_report(self):

        # Init WPWatcher
        wpwatcher = WPWatcher(
            Config.fromstring(DEFAULT_CONFIG + "\nattach_wpscan_output=Yes"))

        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)
            }

            wpwatcher.scanner.mail._send_report(report,
                                                email_to='test',
                                                wpscan_command='just testing')
Exemple #11
0
    def test_read_config_error(self):

        with self.assertRaisesRegex(
            (ValueError),
                'Make sure the file exists and you have correct access right'):
            Config.fromfiles(['/tmp/this_file_is_inexistent.conf'])

        WRONG_CONFIG = DEFAULT_CONFIG + '\nverbose=I dont know'

        with self.assertRaisesRegex(
                ValueError, 'Could not read boolean value in config file'):
            Config.fromstring(WRONG_CONFIG)

        WRONG_CONFIG = DEFAULT_CONFIG + '\nwpscan_args=["forgot", "a" "commas"]'

        with self.assertRaisesRegex(
                ValueError, 'Could not read JSON value in config file'):
            Config.fromstring(WRONG_CONFIG)
Exemple #12
0
 def _find_db_file(daemon: bool = False) -> str:
     files = [DEFAULT_REPORTS] if not daemon else [DEFAULT_REPORTS_DAEMON]
     env = ["HOME", "PWD", "XDG_CONFIG_HOME", "APPDATA"]
     return Config.find_files(env, files, "[]", create=True)[0]
Exemple #13
0
    def test_update_report(self):
        # Init Scanner
        scanner = Scanner(Config.fromstring(DEFAULT_CONFIG))
        for s in WP_SITES:
            old=ScanReport({
                    "site": s['url'],
                    "status": "WARNING",
                    "datetime": "2020-04-08T16-05-16",
                    "last_email": "2020-04-08T16-05-17",
                    "error": '',
                    "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"],
                    "summary":None,
                    "wpscan_output":""
                })
            parser = WPScanJsonParser(data={})
            new=ScanReport({
                    "site": s['url'],
                    "status": "",
                    "datetime": "2020-04-10T16-00-00",
                    "last_email": None,
                    "error": '',
                    "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": [],
                    "summary":None,
                    "wpscan_parser": parser,
                    "wpscan_output":""
                })

            expected=ScanReport({
                    "site": s['url'],
                    "status": "WARNING",
                    "datetime": "2020-04-10T16-00-00",
                    "last_email": "2020-04-08T16-05-17",
                    "error": '',
                    "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\nWarning: This issue is unfixed since 2020-04-08T16-05-16"
                    ],
                    "alerts": [],
                    "fixed": [
                        "This issue was fixed",
                        'Issue regarding component "%s" has been fixed since the last scan.'%("[+] WordPress version 5.2.2 identified (Insecure, released on 2019-06-18).")    
                    ],
                    "summary":None,
                    "wpscan_parser": parser,
                    "wpscan_output":""
                })
            
            new.update_report(old)
            self.assertEqual(dict(new), dict(expected), "There is an issue with fixed issues feature: the expected report do not match the report returned by update_report()")
Exemple #14
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))
Exemple #15
0
    def test_wp_reports_read_write(self):
        SPECIFIC_WP_REPORTS_FILE_CONFIG = DEFAULT_CONFIG + "\nwp_reports=%s"

        # Compare with config and no config
        db = DataBase()
        paths_found = db._find_db_file()
        db2 = DataBase(
            filepath=Config.fromstring(SPECIFIC_WP_REPORTS_FILE_CONFIG %
                                       (paths_found))['wp_reports'])
        self.assertEqual(
            db._data, db2._data,
            "WP reports database are different even if files are the same")

        # Test Reports database
        reports = [
            ScanReport({
                "site":
                "exemple.com",
                "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": []
            }),
            ScanReport({
                "site": "exemple2.com",
                "status": "INFO",
                "datetime": "2020-04-08T16-05-16",
                "last_email": None,
                "error": '',
                "infos": ["[+]", "blablabla"],
                "warnings": [],
                "alerts": [],
                "fixed": []
            })
        ]

        db = DataBase()
        db.open()
        db.write(reports)
        db.close()

        # Test internal _data gets updated after write() method
        for r in reports:
            self.assertIn(
                r, db._data,
                "The report do not seem to have been saved into WPWatcher.wp_report list"
            )

        # Test write method
        wrote_db = ReportCollection(
            ScanReport(item) for item in db._build_db(db.filepath))
        with open(db.filepath, 'r') as dbf:
            wrote_db_alt = ReportCollection(
                ScanReport(item) for item in json.load(dbf))
        for r in reports:
            self.assertIn(
                r, list(wrote_db),
                "The report do not seem to have been saved into db file")
            self.assertIn(
                r, list(wrote_db_alt),
                "The report do not seem to have been saved into db file (directly read with json.load)"
            )
            self.assertIsNotNone(
                db.find(ScanReport(site=r['site'])),
                "The report do not seem to have been saved into db, cannot find it using find(). "
            )
        self.assertEqual(
            list(db._data), list(wrote_db_alt),
            "The database file wrote (directly read with json.load) differ from in memory database"
        )
        self.assertEqual(
            list(db._data), list(wrote_db),
            "The database file wrote differ from in memory database")
    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(Config.fromstring(CONFIG1))

        # Run scans
        res1 = w1.run_scans_and_notify()

        # Init WPWatcher
        w2 = WPWatcher(Config.fromstring(CONFIG2))

        # Run scans
        res2 = w2.run_scans_and_notify()

        # Init WPWatcher
        w3 = WPWatcher(Config.fromstring(CONFIG3))

        # 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")
Exemple #17
0
    def test_interrupt(self):
        wpwatcher = WPWatcher(Config.fromstring(DEFAULT_CONFIG))

        with self.assertRaises(SystemExit):
            wpwatcher.interrupt()