diff --git a/docs/src/usage/plugins/scratchspace.md b/docs/src/usage/plugins/scratchspace.md index 3be8c3b..fd4a0a8 100644 --- a/docs/src/usage/plugins/scratchspace.md +++ b/docs/src/usage/plugins/scratchspace.md @@ -16,21 +16,39 @@ >> '/tmp/Scratchspace/ADAMANTANT/test.txt' >> ``` +## scratchspace.set_assets_file(content, target=None, codename=None) + +> This function will save a `assets.txt` scope file within a `codename` folder in within the `self.db.scratchspace_dir` folder +> If `self.db.use_scratchspace` is `True`, this function is automatically run when you do `targets.get_scope()` or `targets.get_scope_web()` +> +> | Arguments | Type | Description +> | --- | --- | --- +> | `content` | str,list(str) | Either a preformatted string or (more likely) the return of `targets.get_scope()` +> | `target` | db.models.Target | A Target Database Object +> | `codename` | str | Codename of a Target +> +>> Examples +>> ```python3 +>> >>> scope = h.targets.get_scope_web(codename='ADAMANTANT') +>> >>> h.scratchspace.set_assets_file(scope, codename='ADAMANTANT') +>> '/tmp/Scratchspace/ADAMANTANT/assets.txt' +>> ``` + ## scratchspace.set_burp_file(content, target=None, codename=None) > This function will save a `burp.txt` scope file within a `codename` folder in within the `self.db.scratchspace_dir` folder -> If `self.db.use_scratchspace` is `True`, this function is automatically run when you do `self.targets.get_scope()` or `self.targets.get_scope_web()` +> If `self.db.use_scratchspace` is `True`, this function is automatically run when you do `targets.get_scope()` or `targets.get_scope_web()` > > | Arguments | Type | Description > | --- | --- | --- -> | `content` | str,list(str) | Either a preformatted string or (more likely) the return of `self.targets.get_scope_host()` +> | `content` | str,list(str) | Either a preformatted string or (more likely) the return of `targets.get_scope()` > | `target` | db.models.Target | A Target Database Object > | `codename` | str | Codename of a Target > >> Examples >> ```python3 >> >>> scope = h.targets.get_scope_web(codename='ADAMANTANT') ->> >>> h.scratchspace.set_hosts_file(scope, codename='ADAMANTANT') +>> >>> h.scratchspace.set_burp_file(scope, codename='ADAMANTANT') >> '/tmp/Scratchspace/ADAMANTANT/burp.txt' >> ``` @@ -67,11 +85,11 @@ ## scratchspace.set_hosts_file(content, target=None, codename=None) > This function will save a `hosts.txt` scope file within a `codename` folder in within the `self.db.scratchspace_dir` folder. -> If `self.db.use_scratchspace` is `True`, this function is automatically run when you do `self.targets.get_scope()` or `self.targets.get_scope_host()` +> If `self.db.use_scratchspace` is `True`, this function is automatically run when you do `targets.get_scope()` or `targets.get_scope_host()` > > | Arguments | Type | Description > | --- | --- | --- -> | `content` | str,list(str) | Either a preformatted string or (more likely) the return of `self.targets.get_scope_host()` +> | `content` | str,list(str) | Either a preformatted string or (more likely) the return of `targets.get_scope_host()` > | `target` | db.models.Target | A Target Database Object > | `codename` | str | Codename of a Target > diff --git a/docs/src/usage/plugins/targets.md b/docs/src/usage/plugins/targets.md index 202b0c7..2135395 100644 --- a/docs/src/usage/plugins/targets.md +++ b/docs/src/usage/plugins/targets.md @@ -72,7 +72,7 @@ >> [ >> { >> 'target': '94hw8ier', ->> 'urls': [{'url': https://good.monkey.com'}] +>> 'urls': [{'url': 'https://good.monkey.com'}] >> }, >> ... >> ] @@ -120,6 +120,38 @@ >> [{"id": 1, ...},...] >> ``` +## targets.get_assets(self, target=None, asset_type=None, host_type=None, active='true', scope=['in', 'discovered'], sort='location', sort_dir='asc', page=None, organization_uid=None, **kwargs) + +> Pull back a list of assets related to a target. +> +> If no arguments are provided, whatever target you are currently connected to will be queried with the default paramters. +> You can use the following arguments to specify a target/organization or override default parameters. +> +> Note that `scopeRules` and `listings` both have a `scope` field, which is confusing. +> From the best I can tell, the one in `listings` is the one that matters. +> PLEASE double check my math before trusting it blindly and let me know if I'm wrong. +> +> | Arguments | Type | Description +> | --- | --- | --- +> | `target` | db.models.Target | A single Target returned from the database +> | `asset_type` | str | Type of target ('host', 'webapp', etc.) +> | `host_type` | str | Type of information to get back ('cidr') +> | `active` | str | This field appears to specify whether the asset is an active item in the target's scope +> | `scope` | str | I'm honestly not entirely sure what this field is, but the default is ['in', 'discovered'] when made officially. +> | `sort_dir` | str | SQL-type sort direction (`asc`, `desc`) +> | `page` | int | The page of assets to return +> | `organization_uid` | str | slug of the organization that owns the target +> +>> Examples +>> ```python3 +>> >>> h.targets.get_assets() +>> [{ +>> 'listings': [{'listingUid': 'iuqwehuh4', 'scope': 'in'}, ...], +>> 'location': 'https://www.something.com (https://www.something.com)', +>> 'scopeRules': [{'appliesTo': 'both', 'rule': '*.www.something.com/*', 'uid': 'qiuwe'}, ...], +>> ... +>> }, ...] + ## targets.get_attachments(target, **kwargs) > Gets the attachments of a specific target. diff --git a/src/synack/plugins/alerts.py b/src/synack/plugins/alerts.py index 3fc0f08..92086c7 100644 --- a/src/synack/plugins/alerts.py +++ b/src/synack/plugins/alerts.py @@ -39,11 +39,11 @@ def email(self, subject='Test Alert', message='This is a test'): def sanitize(self, message): message = re.sub(r'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}.[0-9]{1,3}', '[IPv4]', message) - message = re.sub(r'(?:https?:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}' + + message = re.sub(r'(?:h[tx]{1,2}ps?:\/\/)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{2,6}' + r'\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\\/=]*)', '[URL]', message) - message = re.sub(r'(?:https?:\/\/)?(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}' + - r'\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)', '[URL]', message) - message = re.sub(r'[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b' + + message = re.sub(r'(?:h[xt]{1,2}ps?:\/\/)?(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}' + + r'\\.[a-zA-Z0-9()]{2,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)', '[URL]', message) + message = re.sub(r'[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{2,6}\b' + r'(?:[-a-zA-Z0-9()@:%_\+.~#?&\\/=]*)', '[URL]', message) message = re.sub(r'(?:^|(?<=\s))(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|' + r'([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|' + diff --git a/src/synack/plugins/scratchspace.py b/src/synack/plugins/scratchspace.py index b6cb297..436e496 100644 --- a/src/synack/plugins/scratchspace.py +++ b/src/synack/plugins/scratchspace.py @@ -27,6 +27,15 @@ def build_filepath(self, filename, target=None, codename=None): f = f / filename return f + def set_assets_file(self, content, target=None, codename=None): + if target or codename: + if type(content) in [list, set]: + content = '\n'.join(content) + dest_file = self.build_filepath('assets.txt', target=target, codename=codename) + with open(dest_file, 'w') as fp: + fp.write(content) + return dest_file + def set_burp_file(self, content, target=None, codename=None): if target or codename: if type(content) == dict: @@ -55,7 +64,7 @@ def set_download_attachments(self, attachments, target=None, codename=None, prom def set_hosts_file(self, content, target=None, codename=None): if target or codename: - if type(content) == list: + if type(content) in [list, set]: content = '\n'.join(content) dest_file = self.build_filepath('hosts.txt', target=target, codename=codename) with open(dest_file, 'w') as fp: diff --git a/src/synack/plugins/targets.py b/src/synack/plugins/targets.py index 6848c4a..2e13575 100644 --- a/src/synack/plugins/targets.py +++ b/src/synack/plugins/targets.py @@ -4,6 +4,7 @@ """ import ipaddress +import re from urllib.parse import urlparse from .base import Plugin @@ -45,45 +46,41 @@ def build_scope_host_db(self, slug, scope): def build_scope_web_burp(self, scope): """Return a Burp Suite scope given retrieved web scope""" - ret = {'target': {'scope': {'advanced_mode': 'true', 'exclude': [], 'include': []}}} + ret = {'target': {'scope': {'advanced_mode': 'true', 'exclude': list(), 'include': list()}}} + for asset in scope: - state = 'include' if asset['status'] == 'in' else 'exclude' - raw = urlparse(asset['raw_url']) - for item in asset['rules']: - item = item['rule'].strip('.*') - url = urlparse(item) - if len(url.netloc) == 0: - url = urlparse(raw.scheme + '://' + item) - ret['target']['scope'][state].append({ - 'enabled': True if url.hostname else False, - 'scheme': url.scheme if url.scheme else 'any', - 'host': url.hostname, - 'file': url.path - }) + state = 'include' if asset.get('status') == 'in' else 'exclude' + raw = urlparse(asset.get('location', '')) + item = asset.get('rule', '').strip('.*') + url = urlparse(item) + if len(url.netloc) == 0: + url = urlparse(raw.scheme + '://' + item) + ret['target']['scope'][state].append({ + 'enabled': True if url.hostname else False, + 'scheme': url.scheme if url.scheme else 'any', + 'host': url.hostname, + 'file': url.path + }) return ret def build_scope_web_db(self, scope): """Return a Web Scope that can be ingested into the Database""" + sorting = dict() + + for item in scope: + if item.get('status') == 'in': + slug = item.get('listing') + sorting[slug] = sorting.get(slug, list()) + sorting[slug].append({'url': item.get('location')}) + ret = list() - for asset in scope: - if asset.get('status') == 'in': - for owner in asset.get('owners'): - ret.append({ - "target": owner.get('owner_uid'), - "urls": [{ - "url": asset.get('raw_url') - }] - }) - return ret - def build_scope_web_urls(self, scope): - """Return a list of the raw urls gived a retrieved web scope""" - ret = {"in": list(), "out": list()} - for asset in scope: - if asset.get('status') == 'in': - ret["in"].append(asset["raw_url"]) - else: - ret["out"].append(asset["raw_url"]) + for slug, urls in sorting.items(): + ret.append({ + 'target': slug, + 'urls': urls + }) + return ret def build_slug_from_codename(self, codename): @@ -104,6 +101,49 @@ def get_assessments(self): self.db.add_categories(res.json()) return self.db.categories + def get_assets(self, target=None, asset_type=None, host_type=None, active='true', + scope=['in', 'discovered'], sort='location', sort_dir='asc', + page=None, organization_uid=None, **kwargs): + """Get the assets (scope) of a target""" + if target is None: + if len(kwargs) > 0: + target = self.db.find_targets(**kwargs) + else: + curr = self.get_connected() + target = self.db.find_targets(slug=curr.get('slug')) + + if type(scope) == str: + scope = [scope] + + if target: + if type(target) is list and len(target) > 0: + target = target[0] + queries = list() + + queries.append(f'listingUid%5B%5D={target.slug}') + if organization_uid is not None: + queries.append(f'organizationUid%5B%5D={organization_uid}') + if asset_type is not None: + queries.append(f'assetType%5B%5D={asset_type}') + if host_type is not None: + queries.append(f'hostType%5B%5D={host_type}') + for item in scope: + queries.append(f'scope%5B%5D={item}') + if sort is not None: + queries.append(f'sort%5B%5D={sort}') + if active is not None: + queries.append(f'active={active}') + if sort_dir is not None: + queries.append(f'sortDir={sort_dir}') + if page is not None: + queries.append(f'page={page}') + + res = self.api.request('GET', f'asset/v2/assets?{"&".join(queries)}') + if res.status_code == 200: + if self.db.use_scratchspace: + self.scratchspace.set_assets_file(res.text, target=target) + return res.json() + def get_attachments(self, target=None, **kwargs): """Get the attachments of a target.""" if target is None: @@ -211,37 +251,65 @@ def get_scope(self, add_to_db=False, **kwargs): def get_scope_host(self, target=None, add_to_db=False, **kwargs): """Get the scope of a Host target""" if target is None: - target = self.db.find_targets(**kwargs) - if target: - target = target[0] + targets = self.db.find_targets(**kwargs) + if targets: + target = next(iter(targets), None) + + scope = set() + if target: - res = self.api.request('GET', f'targets/{target.slug}/cidrs?page=all') - if res.status_code == 200: - scope = res.json()['cidrs'] + assets = self.get_assets(target=target, active='true', asset_type='host', host_type='cidr') + for asset in assets: + if asset.get('active'): + try: + ipaddress.IPv4Network(asset.get('location')) + scope.add(asset.get('location')) + except ipaddress.AddressValueError: + # Not actually an IP + pass + + scope.discard(None) + + if len(scope) > 0: if add_to_db: self.db.add_ips(self.build_scope_host_db(target.slug, scope)) if self.db.use_scratchspace: self.scratchspace.set_hosts_file(scope, target=target) - return scope + + return scope def get_scope_web(self, target=None, add_to_db=False, **kwargs): - """Get the web scpope of a Web or Mobile target""" + """Get the scope of a Web target""" if target is None: - target = self.db.find_targets(**kwargs) - if target: - target = target[0] - res = self.api.request('GET', f'asset/v1/organizations/{target.organization}' + - f'/owners/listings/{target.slug}/webapps') - if res.status_code == 200: - scope = list() - for asset in res.json(): - if target.slug in [o['owner_uid'] for o in asset['owners']]: - scope.append(asset) - if add_to_db: - self.db.add_urls(self.build_scope_web_db(scope)) - if self.db.use_scratchspace: - self.scratchspace.set_burp_file(self.build_scope_web_burp(scope), target=target) - return scope + targets = self.db.find_targets(**kwargs) + if targets: + target = next(iter(targets), None) + + scope = list() + + if target: + assets = self.get_assets(target=target, active='true', asset_type='webapp') + for asset in assets: + if asset.get('active'): + location = next(iter(re.split(r' \(', asset.get('location', '')))) + for listing in asset.get('listings', []): + status = listing.get('scope') + listing = listing.get('listingUid') + for rule in asset.get('scopeRules', []): + scope.append({ + 'status': status, + 'listing': listing, + 'location': location, + 'rule': rule.get('rule') + }) + + if len(scope) > 0: + if add_to_db: + self.db.add_urls(self.build_scope_web_db(scope)) + if self.db.use_scratchspace: + self.scratchspace.set_web_file(self.build_scope_web_burp(scope), target=target) + + return scope def get_submissions(self, target=None, status="accepted", **kwargs): """Get the details of previously submitted vulnerabilities from the analytics of a target.""" diff --git a/test/test_alerts.py b/test/test_alerts.py index b114e7f..17086b2 100644 --- a/test/test_alerts.py +++ b/test/test_alerts.py @@ -70,11 +70,13 @@ def test_sanitize_overzealous(self): self.assertEqual(self.alerts.sanitize('This is fine!'), 'This is fine!') self.assertEqual(self.alerts.sanitize('This is: fine'), 'This is: fine') self.assertEqual(self.alerts.sanitize('This is fine?'), 'This is fine?') + self.assertEqual(self.alerts.sanitize('24.0'), '24.0') def test_sanitize_urls(self): """Should sanitize URLs""" self.assertEqual(self.alerts.sanitize('test.cc'), '[URL]') self.assertEqual(self.alerts.sanitize('http://1.2.ewufg.4.test.cc'), '[URL]') + self.assertEqual(self.alerts.sanitize('hxxp://1.2.ewufg.4.test.cc'), '[URL]') self.assertEqual(self.alerts.sanitize('http://1.2.ewufg.4.test.cc:8081'), '[URL]') self.assertEqual(self.alerts.sanitize('http://1.2.ewufg.4.test.cc:8081/tacos/are/number/1'), '[URL]') self.assertEqual(self.alerts.sanitize('URL: https://www.test.com/1/2/3/air'), 'URL: [URL]') diff --git a/test/test_scratchspace.py b/test/test_scratchspace.py index 7132f98..c167f89 100644 --- a/test/test_scratchspace.py +++ b/test/test_scratchspace.py @@ -33,6 +33,28 @@ def test_build_filepath_target(self): ret = self.scratchspace.build_filepath('test.txt', target=target) self.assertEqual(pathlib.Path('/tmp/TIREDTURKEY/test.txt'), ret) + def test_set_asset_file(self): + """Shoudl create an asset file within the correct directory""" + self.scratchspace.build_filepath = MagicMock() + self.scratchspace.build_filepath.return_value = '/tmp/TIREDTURKEY/assets.txt' + m = mock_open() + with patch('builtins.open', m, create=True): + ret = self.scratchspace.set_assets_file('Test', codename='TIREDTURKEY') + self.assertEqual('/tmp/TIREDTURKEY/assets.txt', ret) + m.return_value.write.assert_called_with('Test') + m.assert_called_with('/tmp/TIREDTURKEY/assets.txt', 'w') + + def test_set_asset_file_list(self): + """Shoudl create an asset file within the correct directory""" + self.scratchspace.build_filepath = MagicMock() + self.scratchspace.build_filepath.return_value = '/tmp/TIREDTURKEY/assets.txt' + m = mock_open() + with patch('builtins.open', m, create=True): + ret = self.scratchspace.set_assets_file(['one', 'two', 'three'], codename='TIREDTURKEY') + self.assertEqual('/tmp/TIREDTURKEY/assets.txt', ret) + m.return_value.write.assert_called_with('one\ntwo\nthree') + m.assert_called_with('/tmp/TIREDTURKEY/assets.txt', 'w') + def test_set_burp_file(self): """Should create a burp file within the correct directory""" self.scratchspace.build_filepath = MagicMock() diff --git a/test/test_targets.py b/test/test_targets.py index 46f3c45..8252f4b 100644 --- a/test/test_targets.py +++ b/test/test_targets.py @@ -73,19 +73,22 @@ def test_build_scope_web_burp(self): """Should build a Burp Suite Scope given a Synack API Scope""" scope = [ { - 'raw_url': 'https://good.stuff.com', + 'listing': 'uqwheiuqwhe', + 'location': 'https://good.stuff.com', 'status': 'in', - 'rules': [ - {'rule': '*.stuff.com/*'}, - {'rule': 'https://super.stuff.com/'}, - ] + 'rule': '*.stuff.com/*', }, { - 'raw_url': 'http://evil.stuff.com', + 'listing': 'uqwheiuqwhe', + 'location': 'https://good.stuff.com', + 'status': 'in', + 'rule': 'https://super.stuff.com/' + }, + { + 'listing': 'uqwheiuqwhe', + 'location': 'http://evil.stuff.com', 'status': 'out', - 'rules': [ - {'rule': '*.evil.stuff.com/login/*'}, - ] + 'rule': '*.evil.stuff.com/login/*' } ] expected = { @@ -123,50 +126,40 @@ def test_build_scope_web_db(self): """Should build a web scope that can be ingested into the Database""" scope = [ { - 'raw_url': 'https://good.stuff.com', - 'owners': [{'owner_uid': '12345'}, {'owner_uid': '67890'}], - 'status': 'in' + 'listing': 'uqwheiuq', + 'location': 'https://good.stuff.com', + 'status': 'in', + 'rule': '*.good.stuff.com/*' + }, + { + 'listing': 'uqwheiuq', + 'location': 'https://bad.stuff.com', + 'status': 'out', + 'rule': '*.bad.stuff.com/*' }, { - 'raw_url': 'https://bad.stuff.com', - 'owners': [{'owner_uid': 'abcde'}], - 'status': 'out' + 'listing': '21ye78r3hwe', + 'location': 'https://good.things.com', + 'status': 'in', + 'rule': '*.good.things.com/*' } ] expected = [ { - 'target': '12345', + 'target': 'uqwheiuq', 'urls': [{ 'url': 'https://good.stuff.com' }] }, { - 'target': '67890', + 'target': '21ye78r3hwe', 'urls': [{ - 'url': 'https://good.stuff.com' + 'url': 'https://good.things.com' }] } ] self.assertEqual(expected, self.targets.build_scope_web_db(scope)) - def test_build_scope_web_urls(self): - """Should build dictionaries of Web Application URLs given a Synack API Scope""" - scope = [ - { - 'raw_url': 'https://good.stuff.com', - 'status': 'in' - }, - { - 'raw_url': 'https://bad.stuff.com', - 'status': 'out' - } - ] - expected = { - 'in': ['https://good.stuff.com'], - 'out': ['https://bad.stuff.com'] - } - self.assertEqual(expected, self.targets.build_scope_web_urls(scope)) - def test_build_slug_from_codename(self): """Should return a slug for a given codename""" ret_targets = [Target(slug="qwerty")] @@ -223,6 +216,41 @@ def test_get_assessments_all_passed(self): self.assertEqual([cat1], self.targets.get_assessments()) self.targets.db.add_categories.assert_called_with(assessments) + def test_get_assets(self): + """Should return a list of assets for a currently connected target""" + self.targets.get_connected = MagicMock() + self.targets.get_connected.return_value = {'codename': 'TURBULENTTORTOISE', 'slug': '327h8iw'} + self.targets.db.find_targets.return_value = [Target(slug='327h8iw')] + self.targets.api.request.return_value.status_code = 200 + self.targets.api.request.return_value.text = 'rettext' + self.targets.api.request.return_value.json.return_value = 'retjson' + self.assertEqual('retjson', self.targets.get_assets()) + self.targets.api.request.assert_called_with('GET', + 'asset/v2/assets?listingUid%5B%5D=327h8iw&scope%5B%5D=in' + + '&scope%5B%5D=discovered&sort%5B%5D=location&active=true' + + '&sortDir=asc') + + def test_get_assets_non_defaults(self): + """Should return a list of assets given information to query""" + self.targets.db.find_targets.return_value = [Target(codename='TURBULENTTORTOISE', slug='327h8iw')] + self.targets.api.request.return_value.status_code = 200 + self.targets.api.request.return_value.text = 'rettext' + self.targets.api.request.return_value.json.return_value = 'retjson' + self.assertEqual('retjson', self.targets.get_assets(codename='TURBULENTTORTOISE', + asset_type='blah', + host_type='applecidr', + active='false', + scope='secret', + sort='vulnerable', + sort_dir='desc', + page=3, + organization_uid='uiehqw')) + self.targets.api.request.assert_called_with('GET', + 'asset/v2/assets?listingUid%5B%5D=327h8iw' + + '&organizationUid%5B%5D=uiehqw&assetType%5B%5D=blah' + + '&hostType%5B%5D=applecidr&scope%5B%5D=secret' + + '&sort%5B%5D=vulnerable&active=false&sortDir=desc&page=3') + def test_get_attachments_current(self): """Should return a list of attachments based on currently selected target""" attachments = [ @@ -491,33 +519,64 @@ def test_get_scope_for_web_add_to_db(self): def test_get_scope_host(self): """Should get the scope for a Host""" - ips = ['1.1.1.1/32', '2.2.2.2/32'] - self.targets.api.request.return_value.status_code = 200 - self.targets.api.request.return_value.json.return_value = { - 'cidrs': ips - } + ips = {'1.1.1.1/32', '2.2.2.2/32'} + self.targets.get_assets = MagicMock() + self.targets.get_assets.return_value = [ + { + 'active': True, + 'location': '1.1.1.1/32' + }, + { + 'active': True, + 'location': '2.2.2.2/32' + } + ] self.targets.db.find_targets.return_value = [Target(slug='213h89h3', codename='SASSYSQUIRREL')] out = self.targets.get_scope_host(codename='SASSYSQUIRREL') self.assertEqual(ips, out) self.targets.db.find_targets.assert_called_with(codename='SASSYSQUIRREL') - self.targets.api.request.assert_called_with('GET', 'targets/213h89h3/cidrs?page=all') - self.targets.api.request.return_value.json.assert_called() def test_get_scope_host_add_to_db(self): """Should get the scope for a Host""" - ips = ['1.1.1.1/32', '2.2.2.2/32'] - self.targets.api.request.return_value.status_code = 200 - self.targets.api.request.return_value.json.return_value = { - 'cidrs': ips - } + ips = {'1.1.1.1/32', '2.2.2.2/32'} + self.targets.get_assets = MagicMock() + self.targets.get_assets.return_value = [ + { + 'active': True, + 'location': '1.1.1.1/32' + }, + { + 'active': True, + 'location': '2.2.2.2/32' + } + ] + self.targets.build_scope_host_db = MagicMock() + self.targets.build_scope_host_db.return_value = 'host_db_return_value' self.targets.db.find_targets.return_value = [Target(slug='213h89h3', codename='SASSYSQUIRREL')] out = self.targets.get_scope_host(codename='SASSYSQUIRREL', add_to_db=True) self.assertEqual(ips, out) self.targets.db.find_targets.assert_called_with(codename='SASSYSQUIRREL') - self.targets.api.request.assert_called_with('GET', 'targets/213h89h3/cidrs?page=all') - self.targets.api.request.return_value.json.assert_called() - self.targets.db.add_ips.assert_called_with([{'target': '213h89h3', 'ip': '1.1.1.1'}, - {'target': '213h89h3', 'ip': '2.2.2.2'}]) + self.targets.build_scope_host_db.assert_called_with('213h89h3', ips) + self.targets.db.add_ips.assert_called_with('host_db_return_value') + + def test_get_scope_host_not_ip(self): + """Should get the scope for a Host""" + ips = {'1.1.1.1/32'} + self.targets.get_assets = MagicMock() + self.targets.get_assets.return_value = [ + { + 'active': True, + 'location': '1.1.1.1/32' + }, + { + 'active': True, + 'location': '8675309' + } + ] + self.targets.db.find_targets.return_value = [Target(slug='213h89h3', codename='SASSYSQUIRREL')] + out = self.targets.get_scope_host(codename='SASSYSQUIRREL') + self.assertEqual(ips, out) + self.targets.db.find_targets.assert_called_with(codename='SASSYSQUIRREL') def test_get_scope_no_provided(self): """Should get the scope for the currently connected target if none is specified""" @@ -530,46 +589,60 @@ def test_get_scope_no_provided(self): def test_get_scope_web(self): """Should get the scope for a Web Application""" - self.targets.api.request.return_value.status_code = 200 self.targets.build_scope_web_burp = MagicMock() - web_results = [{ - 'web_results': 'yup these them!', - 'owners': [{ - 'owner_uid': '213h89h3' - }], + scope = [{ + 'listing': 'uewqhuiewq', + 'location': 'https://good.things.com', + 'rule': '*.good.things.com/*', + 'status': 'in' }] - self.targets.api.request.return_value.json.return_value = web_results + self.targets.get_assets = MagicMock() + self.targets.get_assets.return_value = [ + { + 'active': True, + 'listings': [{'listingUid': 'uewqhuiewq', 'scope': 'in'}], + 'location': 'https://good.things.com (https://good.things.com)', + 'scopeRules': [ + {'rule': '*.good.things.com/*'} + ] + } + ] tgt = Target(slug='213h89h3', organization='93g8eh8', codename='SASSYSQUIRREL') self.targets.db.find_targets.return_value = [tgt] out = self.targets.get_scope_web(codename='SASSYSQUIRREL') - self.assertEqual(web_results, out) - self.targets.build_scope_web_burp.assert_called_with(web_results) + self.assertEqual(scope, out) + self.targets.build_scope_web_burp.assert_called_with(scope) self.targets.db.find_targets.assert_called_with(codename='SASSYSQUIRREL') - self.targets.api.request.assert_called_with('GET', - 'asset/v1/organizations/93g8eh8/owners/listings/213h89h3/webapps') - self.targets.api.request.return_value.json.assert_called() + self.targets.get_assets.assert_called_with(target=tgt, active='true', asset_type='webapp') def test_get_scope_web_add_to_db(self): - """Should get the scope for a Web Application""" - self.targets.api.request.return_value.status_code = 200 + """Should get the scope for a Web Application and add it to the database""" self.targets.build_scope_web_burp = MagicMock() self.targets.build_scope_web_db = MagicMock() - web_results = [{ - 'web_results': 'yup these them!', - 'owners': [{ - 'owner_uid': '213h89h3' - }], + self.targets.get_assets = MagicMock() + scope = [{ + 'listing': 'uewqhuiewq', + 'location': 'https://good.things.com', + 'rule': '*.good.things.com/*', + 'status': 'in' }] - self.targets.api.request.return_value.json.return_value = web_results + self.targets.get_assets = MagicMock() + self.targets.get_assets.return_value = [ + { + 'active': True, + 'listings': [{'listingUid': 'uewqhuiewq', 'scope': 'in'}], + 'location': 'https://good.things.com (https://good.things.com)', + 'scopeRules': [ + {'rule': '*.good.things.com/*'} + ] + } + ] tgt = Target(slug='213h89h3', organization='93g8eh8', codename='SASSYSQUIRREL') self.targets.db.find_targets.return_value = [tgt] out = self.targets.get_scope_web(codename='SASSYSQUIRREL', add_to_db=True) - self.assertEqual(web_results, out) - self.targets.build_scope_web_burp.assert_called_with(web_results) + self.assertEqual(scope, out) + self.targets.build_scope_web_burp.assert_called_with(scope) self.targets.db.find_targets.assert_called_with(codename='SASSYSQUIRREL') - self.targets.api.request.assert_called_with('GET', - 'asset/v1/organizations/93g8eh8/owners/listings/213h89h3/webapps') - self.targets.api.request.return_value.json.assert_called() self.targets.db.add_urls.assert_called_with(self.targets.build_scope_web_db.return_value) def test_get_submissions(self):