def _init_scanner(self): self.scanner = Drupal() self.scanner._general_init(self.test_opts) self.scanner._determine_fake_200_module = self._fake_200_check m = self.mock_controller('drupal', '_determine_fake_200_module', return_value=False)
def test_fix_dereference_bug(self): ''' test for dereference that made the app fail even though all tests were passing. ''' plugins_base_url = 'plugins_base_url' themes_base_url = 'themes_base_url' opts_p = { 'url': self.base_url, 'plugins_base_url': plugins_base_url, 'themes_base_url': themes_base_url, 'scanning_method': 'a', 'number': 'a', 'threads': 'a', 'threads_enumerate': None, 'threads_identify': None, 'threads_scan': None, 'verb': 'a', 'enumerate': 'p', 'timeout': 15, 'headers': {} } opts_t = dict(opts_p) opts_t['enumerate'] = 't' drupal = Drupal() kwargs_p = drupal._functionality(opts_p)['plugins']['kwargs'] kwargs_t = drupal._functionality(opts_t)['themes']['kwargs'] # these should not be equal assert not kwargs_p == kwargs_t
def test_module_fake_200(self, warn, mock): """ The workaround implemented to find some modules that return 200 when they are present causes other sites to report many false positives. This is due to the fact that all modules respond with 200 for unknown reasons. In these cases 200 should be ignored as they are fake. """ scanner = Drupal() scanner._general_init(self.test_opts) r_404 = ['supermodule/'] r_403 = ['yep/', 'thisisthere/', 'thisisalsothere/'] r_500 = ['iamtherebuti500/'] r_200 = ['iamtherebuti200/', scanner.not_found_module + "/"] self.respond_several(self.base_url + 'sites/all/modules/%s', {404: r_404, 403: r_403, 500: r_500, 200: r_200}) scanner.plugins_base_url = '%ssites/all/modules/%s/' self.mock_controller('drupal', 'enumerate_interesting') result, empty = scanner.enumerate_plugins(self.base_url, scanner.plugins_base_url, ScanningMethod.forbidden) assert len(result) == 4 found_500 = False found_200 = False for res in result: if res['name'] == 'iamtherebuti500': found_500 = True if res['name'] == 'iamtherebuti200': found_200 = True assert found_500 assert not found_200 # 200 should not count as false positive assert warn.called
def test_plugins_update_check(self): drupal = Drupal() drupal.update_plugins = up = Mock(spec=self.scanner.update_plugins, return_value=([], [])) today = datetime.today() yesterday = datetime.today() - timedelta(days=1) too_long_ago = today - timedelta(days=400) o = mock_open() with patch('dscan.plugins.update.open', o, create=True): with patch('dscan.common.update_api.file_mtime', return_value=yesterday, autospec=True): self.updater.update_plugins( self.controller_get('drupal')(), 'Drupal') assert not up.called with patch('dscan.common.update_api.file_mtime', return_value=too_long_ago): self.updater.update_plugins(drupal, 'Drupal') assert up.called
def test_module_fake_200(self, warn, mock): """ The workaround implemented to find some modules that return 200 when they are present causes other sites to report many false positives. This is due to the fact that all modules respond with 200 for unknown reasons. In these cases 200 should be ignored as they are fake. """ scanner = Drupal() scanner._general_init(self.test_opts) r_404 = ['supermodule/'] r_403 = ['yep/', 'thisisthere/', 'thisisalsothere/'] r_500 = ['iamtherebuti500/'] r_200 = ['iamtherebuti200/', scanner.not_found_module + "/"] self.respond_several(self.base_url + 'sites/all/modules/%s', { 404: r_404, 403: r_403, 500: r_500, 200: r_200 }) scanner.plugins_base_url = '%ssites/all/modules/%s/' self.mock_controller('drupal', 'enumerate_interesting') result, empty = scanner.enumerate_plugins(self.base_url, scanner.plugins_base_url, ScanningMethod.forbidden) assert len(result) == 4 found_500 = False found_200 = False for res in result: if res['name'] == 'iamtherebuti500': found_500 = True if res['name'] == 'iamtherebuti200': found_200 = True assert found_500 assert not found_200 # 200 should not count as false positive assert warn.called
def _init_scanner(self): self.scanner = Drupal() self.scanner._general_init(self.test_opts)
class BaseTest(test.CementTestCase): app_class = DroopeScan scanner = None base_url = BASE_URL base_url_https = BASE_URL_HTTPS valid_file = VALID_FILE valid_file_ip = VALID_FILE_IP empty_file = EMPTY_FILE param_base = ["--url", base_url, '-n', '10'] param_plugins = param_base + ["-e", 'p'] param_interesting = param_base + ["-e", 'i'] param_themes = param_base + ["-e", 't'] param_version = param_base + ["-e", 'v'] param_all = param_base + ["-e", 'a'] versions_xsd = 'dscan/common/versions.xsd' xml_file = 'dscan/tests/resources/versions.xml' test_opts = { 'output': 'standard', 'debug_requests': False, 'error_log': '-', 'threads': 1, 'threads_enumerate': None, 'threads_identify': None, 'threads_scan': None, 'verb': 'head', 'timeout': 300, 'plugins_base_url': None, 'themes_base_url': None, 'number': 10, 'debug': False, 'enumerate': 'a', 'headers': {} } host_header = {'Host': 'example.com'} def setUp(self): super(BaseTest, self).setUp() self.reset_backend() defaults = init_defaults('DroopeScan', 'general') defaults['general']['pwd'] = os.getcwd() self.app = DroopeScan(argv=[], plugin_config_dir=dscan.PWD + "./plugins.d", plugin_dir=dscan.PWD + "./plugins", config_defaults=defaults) handler.register(Scan) self.app.testing = True self.app.setup() responses.add(responses.HEAD, self.base_url, status=200) def _init_scanner(self): self.scanner = Drupal() self.scanner._general_init(self.test_opts) def tearDown(self): self.app.close() def mock_controller(self, plugin_label, method, return_value = None, side_effect = None, mock = None): """ Mocks controller by label. Can only be used to test controllers that get instantiated automatically by cement. @param plugin_label: e.g. 'drupal' @param method: e.g. 'enumerate_plugins' @param return_value: what to return. Default is None, unless the method starts with enumerate_*, in which case the result is a tuple as expected by BasePlugin. @param mock: the MagicMock to place. If None, a blank MagicMock is created. @param side_effect: if set to an exception, it will raise an exception. """ if mock: m = mock else: m = MagicMock() if return_value != None: m.return_value = return_value else: if method.startswith("enumerate_"): m.return_value = ({"a":[]}, True) if side_effect: m.side_effect = side_effect setattr(self.controller_get(plugin_label), method, m) return m def controller_get(self, plugin_label): return backend.__handlers__['controller'][plugin_label] def add_argv(self, argv): """ Concatenates list with self.app.argv. """ self.app._meta.argv += argv def clear_argv(self): self.app._meta.argv = [] def assert_called_contains(self, mocked_method, kwarg_name, kwarg_value): """ Assert kwarg_name: equals kwarg name in call to mocked_method. @param mocked_method: mock to check the call to. @param kwarg_name: name of the param. E.g. 'url' @param kwarg_value: expected value. E.g. 'https://www.drupal.org/' """ args, kwargs = mocked_method.call_args assert kwargs[kwarg_name] == kwarg_value, "Parameter is not as expected." def assert_called_contains_all(self, mocked_method, kwarg_name, kwarg_value): call_list = mocked_method.call_args_list if len(mocked_method.call_args_list) == 0: assert False, "No calls to mocked method" for args, kwargs in call_list: assert kwargs[kwarg_name] == kwarg_value def assert_args_contains(self, mocked_method, position, expected_value): """ Assert that the call contains this argument in the args at position position. """ args, kwargs = mocked_method.call_args assert args[position] == expected_value def respond_several(self, base_url, data_obj, verb=responses.HEAD, headers=[]): for status_code in data_obj: for item in data_obj[status_code]: url = base_url % item responses.add(verb, url, body=str(status_code), status=status_code, adding_headers=headers) def mock_all_enumerate(self, plugin_name): all = [] all.append(self.mock_controller(plugin_name, 'enumerate_plugins')) all.append(self.mock_controller(plugin_name, 'enumerate_themes')) all.append(self.mock_controller(plugin_name, 'enumerate_interesting')) all.append(self.mock_controller(plugin_name, 'enumerate_version')) for a in all: all[a].return_value = ([], True) return all def mock_all_url_file(self, url_file): with open(url_file) as f: for url in f: url_tpl = url.strip('\n') + '%s' self.respond_several(url_tpl, { 403: [Drupal.forbidden_url], 200: ['', 'misc/drupal.js'], 404: [self.scanner.not_found_url] }) def mock_xml(self, xml_file, version_to_mock): ''' Generates all mock data, and returns a MagicMock which can be used to replace self.scanner.enumerate_file_hash. self.scanner.enumerate_file_hash = self.mock_xml(self.xml_file, "7.27") @param xml_file: a file, which contains the XML to mock. @param version_to_mock: the version which we will pretend to be. @return: a function which can be used to mock BasePlugin.enumerate_file_hash ''' with open(xml_file) as f: doc = etree.fromstring(f.read()) files_xml = doc.xpath('//cms/files/file') files = {} for file in files_xml: url = file.get('url') versions = file.xpath('version') for file_version in versions: version_number = file_version.get('nb') md5 = file_version.get('md5') if version_number == version_to_mock: files[url] = md5 if not url in files: files[url] = '5d41402abc4b2a76b9719d911017c592' ch_xml_all = doc.findall('./files/changelog') if len(ch_xml_all) > 0: for ch_xml in ch_xml_all: ch_url = ch_xml.get('url') ch_versions = ch_xml.findall('./version') found = False for ch_version in ch_versions: ch_nb = ch_version.get('nb') if ch_nb == version_to_mock: files[ch_url] = ch_version.get('md5') found = True mock_hash = MockHash() mock_hash.files = files mock = MagicMock(side_effect=mock_hash.mock_func) return mock def get_dispatched_controller(self, app): """ This might be considered a hack. I should eventually get in touch with the cement devs and ask for a better alternative :P. """ return app.controller._dispatch_command['controller']\ ._dispatch_command['controller']
def test_kali_old_requests_bug(self, warn): drupal = Drupal() with patch('requests.adapters', spec_set=["force_attr_error"]): drupal._general_init(self.test_opts) assert warn.called
class BaseTest(test.CementTestCase): app_class = DroopeScan scanner = None base_url = BASE_URL base_url_https = BASE_URL_HTTPS valid_file = VALID_FILE valid_file_ip = VALID_FILE_IP empty_file = EMPTY_FILE param_base = ["--url", base_url, '-n', '10'] param_plugins = param_base + ["-e", 'p'] param_interesting = param_base + ["-e", 'i'] param_themes = param_base + ["-e", 't'] param_version = param_base + ["-e", 'v'] param_all = param_base + ["-e", 'a'] versions_xsd = 'dscan/common/versions.xsd' xml_file = 'dscan/tests/resources/versions.xml' test_opts = { 'output': 'standard', 'debug_requests': False, 'error_log': '-', 'threads': 1, 'threads_enumerate': None, 'threads_identify': None, 'threads_scan': None, 'verb': 'head', 'timeout': 300, 'plugins_base_url': None, 'themes_base_url': None, 'number': 10, 'debug': False, 'enumerate': 'a', 'headers': {}, 'hide_progressbar': False, 'user_agent': 'Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0' } host_header = {'Host': 'example.com'} def setUp(self): super(BaseTest, self).setUp() self.reset_backend() defaults = init_defaults('DroopeScan', 'general') defaults['general']['pwd'] = os.getcwd() self.app = DroopeScan(argv=[], plugin_config_dir=dscan.PWD + "./plugins.d", plugin_dir=dscan.PWD + "./plugins", config_defaults=defaults) handler.register(Scan) self.app.testing = True self.app.setup() responses.add(responses.HEAD, self.base_url, status=200) def _init_scanner(self): self.scanner = Drupal() self.scanner._general_init(self.test_opts) self.scanner._determine_fake_200_module = self._fake_200_check m = self.mock_controller('drupal', '_determine_fake_200_module', return_value=False) def _fake_200_check(self, param1, param2, param3): return False def tearDown(self): self.app.close() def mock_controller(self, plugin_label, method, return_value=None, side_effect=None, mock=None): """ Mocks controller by label. Can only be used to test controllers that get instantiated automatically by cement. @param plugin_label: e.g. 'drupal' @param method: e.g. 'enumerate_plugins' @param return_value: what to return. Default is None, unless the method starts with enumerate_*, in which case the result is a tuple as expected by BasePlugin. @param mock: the MagicMock to place. If None, a blank MagicMock is created. @param side_effect: if set to an exception, it will raise an exception. """ if mock: m = mock else: m = MagicMock() if return_value != None: m.return_value = return_value else: if method.startswith("enumerate_"): m.return_value = ({"a": []}, True) if side_effect: m.side_effect = side_effect setattr(self.controller_get(plugin_label), method, m) return m def controller_get(self, plugin_label): return backend.__handlers__['controller'][plugin_label] def add_argv(self, argv): """ Concatenates list with self.app.argv. """ self.app._meta.argv += argv def clear_argv(self): self.app._meta.argv = [] def assert_called_contains(self, mocked_method, kwarg_name, kwarg_value): """ Assert kwarg_name: equals kwarg name in call to mocked_method. @param mocked_method: mock to check the call to. @param kwarg_name: name of the param. E.g. 'url' @param kwarg_value: expected value. E.g. 'https://www.drupal.org/' """ args, kwargs = mocked_method.call_args assert kwargs[ kwarg_name] == kwarg_value, "Parameter is not as expected." def assert_called_contains_all(self, mocked_method, kwarg_name, kwarg_value): call_list = mocked_method.call_args_list if len(mocked_method.call_args_list) == 0: assert False, "No calls to mocked method" for args, kwargs in call_list: assert kwargs[kwarg_name] == kwarg_value def assert_args_contains(self, mocked_method, position, expected_value): """ Assert that the call contains this argument in the args at position position. """ args, kwargs = mocked_method.call_args assert args[position] == expected_value def respond_several(self, base_url, data_obj, verb=responses.HEAD, headers=[]): for status_code in data_obj: for item in data_obj[status_code]: url = base_url % item responses.add(verb, url, body=str(status_code), status=status_code, adding_headers=headers) def mock_all_enumerate(self, plugin_name): all = [] all.append(self.mock_controller(plugin_name, 'enumerate_plugins')) all.append(self.mock_controller(plugin_name, 'enumerate_themes')) all.append(self.mock_controller(plugin_name, 'enumerate_interesting')) for a in all: all[a].return_value = ([], True) mock_version = self.mock_controller(plugin_name, 'enumerate_version') mock_version.return_value = (['7.32'], False) all.append(mock_version) return all def mock_all_url_file(self, url_file): with open(url_file) as f: for url in f: url_tpl = url.strip('\n') + '%s' self.respond_several( url_tpl, { 403: [Drupal.forbidden_url], 200: ['', 'misc/drupal.js'], 404: [ self.scanner.not_found_url, '/sites/all/modules/a12abb4d5bead1220174a6b39a2546db/', ] }) def mock_xml(self, xml_file, version_to_mock): ''' Generates all mock data, and returns a MagicMock which can be used to replace self.scanner.enumerate_file_hash. self.scanner.enumerate_file_hash = self.mock_xml(self.xml_file, "7.27") @param xml_file: a file, which contains the XML to mock. @param version_to_mock: the version which we will pretend to be. @return: a function which can be used to mock BasePlugin.enumerate_file_hash ''' with open(xml_file) as f: doc = etree.fromstring(f.read()) files_xml = doc.xpath('//cms/files/file') files = {} for file in files_xml: url = file.get('url') versions = file.xpath('version') for file_version in versions: version_number = file_version.get('nb') md5 = file_version.get('md5') if version_number == version_to_mock: files[url] = md5 if not url in files: files[url] = '5d41402abc4b2a76b9719d911017c592' ch_xml_all = doc.findall('./files/changelog') if len(ch_xml_all) > 0: for ch_xml in ch_xml_all: ch_url = ch_xml.get('url') ch_versions = ch_xml.findall('./version') found = False for ch_version in ch_versions: ch_nb = ch_version.get('nb') if ch_nb == version_to_mock: files[ch_url] = ch_version.get('md5') found = True mock_hash = MockHash() mock_hash.files = files mock = MagicMock(side_effect=mock_hash.mock_func) return mock def get_dispatched_controller(self, app): """ This might be considered a hack. I should eventually get in touch with the cement devs and ask for a better alternative :P. """ return app.controller._dispatch_command['controller']\ ._dispatch_command['controller']