Skip to content

Commit a1273ba

Browse files
Merge pull request #143 from blacklanternsecurity/fix-target-bug
Fix scope calculation dropping existing assets when target is edited
2 parents f685a8d + d40f3f8 commit a1273ba

2 files changed

Lines changed: 61 additions & 8 deletions

File tree

bbot_server/modules/targets/targets_api.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ async def refresh_asset_scope(self, host: str, target: BBOTTarget, target_id: UU
113113
scope_result_type = getattr(scope_result, "type", None)
114114
if scope_result_type == "NEW_IN_SCOPE_ASSET":
115115
asset_scope = sorted(set(asset_scope) | set([target_id]))
116-
else:
116+
elif scope_result_type == "ASSET_SCOPE_CHANGED":
117117
asset_scope = sorted(set(asset_scope) - set([target_id]))
118118
asset_results = await self.root.assets.collection.update_many(
119119
{"host": host},
@@ -358,17 +358,17 @@ async def _check_scope(self, host, resolved_hosts, target: BBOTTarget, target_id
358358
try:
359359
# we take the main host and its A/AAAA DNS records into account
360360
for rdtype, hosts in resolved_hosts.items():
361-
for host in hosts:
361+
for h in hosts:
362362
# if any of the hosts are blacklisted, abort immediately
363-
if target.blacklisted(host):
364-
blacklisted_reason = f"{rdtype}->{host}"
363+
if target.blacklisted(h):
364+
blacklisted_reason = f"{rdtype}->{h}"
365365
in_target_reason = ""
366366
# break out of the loop
367367
raise BlacklistedError
368368
# check against whitelist
369369
if not in_target_reason:
370-
if target.in_target(host):
371-
in_target_reason = f"{rdtype}->{host}"
370+
if target.in_target(h):
371+
in_target_reason = f"{rdtype}->{h}"
372372
except BlacklistedError:
373373
pass
374374

tests/test_applets/test_applet_targets.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,15 +482,68 @@ async def after_scan_2(self):
482482
assets = [a async for a in self.bbot_server.list_assets()]
483483

484484
# evilcorp.azure.com (127.0.0.3) and evilcorp.amazonaws.com (127.0.0.4) are now part of the target
485-
# 127.0.0.1 no longer exists (it is nowhere to be found in scan 2)
486485
# localhost.evilcorp.com (127.0.0.2) is still blacklisted
487486
target_2_assets = {a.host for a in assets if self.target2.id in a.scope}
488-
assert target_2_assets == {"evilcorp.azure.com", "evilcorp.amazonaws.com"}
487+
assert target_2_assets == {"127.0.0.1", "evilcorp.azure.com", "evilcorp.amazonaws.com"}
489488

490489
async def after_archive(self):
491490
pass
492491

493492

493+
class TestTargetAddDomainPreservesExistingScope(BaseAppletTest):
494+
"""
495+
Regression test for bug where adding a domain to an existing target
496+
caused all previously-in-scope assets to lose their scope, leaving only
497+
the newly added domain in scope.
498+
499+
Root cause: refresh_asset_scope treated a None return from _check_scope
500+
(meaning "no change") as "out of scope", incorrectly removing the target
501+
from assets that were already in scope.
502+
"""
503+
504+
needs_worker = True
505+
506+
async def setup(self):
507+
assert await self.bbot_server.get_hosts() == []
508+
assert await self.bbot_server.get_targets() == []
509+
510+
# create a target with just evilcorp.com
511+
self.target = await self.bbot_server.create_target(
512+
name="evilcorp",
513+
description="evilcorp target",
514+
target=["evilcorp.com"],
515+
)
516+
517+
async def after_scan_1(self):
518+
# verify evilcorp.com assets are in scope
519+
assets = [a async for a in self.bbot_server.list_assets()]
520+
target_assets = {a.host for a in assets if self.target.id in a.scope}
521+
assert "evilcorp.com" in target_assets
522+
assert len(target_assets) > 1, f"Expected multiple evilcorp.com assets in scope, got: {target_assets}"
523+
self.original_target_assets = target_assets
524+
525+
# BUG REPRODUCTION: add a new domain to the target while keeping the existing one
526+
self.target.target = ["evilcorp.com", "testevilcorp.com"]
527+
await self.bbot_server.update_target(self.target.id, self.target)
528+
await asyncio.sleep(1.0)
529+
530+
# verify that existing evilcorp.com assets are STILL in scope
531+
assets = [a async for a in self.bbot_server.list_assets()]
532+
target_assets_after = {a.host for a in assets if self.target.id in a.scope}
533+
534+
# the new domain should also be in scope
535+
assert "testevilcorp.com" in target_assets_after, (
536+
f"Newly added domain testevilcorp.com should be in scope, got: {target_assets_after}"
537+
)
538+
539+
# all previously in-scope assets should still be in scope
540+
missing = self.original_target_assets - target_assets_after
541+
assert not missing, (
542+
f"BUG: These assets lost their scope after adding a domain to the target: {missing}. "
543+
f"Before: {self.original_target_assets}, After: {target_assets_after}"
544+
)
545+
546+
494547
class TestTargetUpdateRemovesTargetFromAssets(BaseAppletTest):
495548
"""
496549
Regression test for bug where editing or deleting a target to remove a domain

0 commit comments

Comments
 (0)