Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event Context #1383

Merged
merged 40 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7c91de6
rename event.source to event.parent
TheTechromancer May 16, 2024
dc30ea7
fix datetime error
TheTechromancer May 16, 2024
59ca592
merge bbot-2.0
TheTechromancer May 16, 2024
5e20e1d
merge bbot-2.0
TheTechromancer May 16, 2024
072c0a1
fix github org tests
TheTechromancer May 16, 2024
b9d2629
fix viewdns tests
TheTechromancer May 17, 2024
9975e82
WIP discovery context
TheTechromancer May 17, 2024
768886b
WIP event discovery context
TheTechromancer May 23, 2024
1f52dc9
event context for excavate
TheTechromancer May 24, 2024
d9a45a8
merge bbot 2.0
TheTechromancer May 24, 2024
03dd785
merge bbot 2.0
TheTechromancer May 24, 2024
57fec03
fix trufflehog tests
TheTechromancer May 24, 2024
52416c7
fix event tests
TheTechromancer May 24, 2024
eedc6cb
steady work on event context
TheTechromancer May 24, 2024
002308c
code enum contexts
TheTechromancer May 24, 2024
15cf732
steady work on event context
TheTechromancer May 26, 2024
9fa7580
fix tests
TheTechromancer May 26, 2024
d2a9280
fix tests
TheTechromancer May 26, 2024
433ff58
merge bbot-2.0
TheTechromancer May 26, 2024
674d074
steady work on event context
TheTechromancer May 26, 2024
46da056
steady work on event context
TheTechromancer May 26, 2024
a3b646c
whoops
TheTechromancer May 26, 2024
13da6de
finished event context first pass
TheTechromancer May 26, 2024
0705113
fix minor module bug
TheTechromancer May 26, 2024
00ecfbd
fix tests
TheTechromancer May 26, 2024
d7aaf69
fix dotnetnuke test
TheTechromancer May 27, 2024
e757cc6
fix skymem
TheTechromancer May 27, 2024
950a7b2
fix masscan bug
TheTechromancer May 27, 2024
b49f282
fix gowitness tests
TheTechromancer May 27, 2024
f4d8419
tests for json and csv
TheTechromancer May 28, 2024
5a034cc
fix tests
TheTechromancer May 28, 2024
685e278
fix github tests
TheTechromancer May 28, 2024
814d73e
blacked
TheTechromancer May 28, 2024
05c9a36
event context ux tweaks
TheTechromancer May 28, 2024
1247d71
fix azure_tenant tests
TheTechromancer May 28, 2024
be814eb
fix gowitness tests
TheTechromancer May 28, 2024
cd91ec1
fix telerik context
TheTechromancer May 28, 2024
6917afc
revised excavate context
TheTechromancer May 29, 2024
325d2ed
blacked
TheTechromancer May 29, 2024
79c59ed
potential
TheTechromancer May 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions bbot/core/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ async def run_and_return(self, client_id, command_fn, *args, **kwargs):
except BaseException as e:
error = f"Error in {self.name}.{command_fn.__name__}({args}, {kwargs}): {e}"
trace = traceback.format_exc()
self.log.error(error)
self.log.trace(trace)
self.log.debug(error)
self.log.debug(trace)
result = {"_e": (error, trace)}
finally:
self.tasks.pop(client_id, None)
Expand All @@ -238,8 +238,8 @@ async def run_and_yield(self, client_id, command_fn, *args, **kwargs):
except BaseException as e:
error = f"Error in {self.name}.{command_fn.__name__}({args}, {kwargs}): {e}"
trace = traceback.format_exc()
self.log.error(error)
self.log.trace(trace)
self.log.debug(error)
self.log.debug(trace)
result = {"_e": (error, trace)}
await self.send_socket_multipart(client_id, result)
finally:
Expand Down
204 changes: 124 additions & 80 deletions bbot/core/event/base.py

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion bbot/core/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"service-enum": "Identifies protocols running on open ports",
"slow": "May take a long time to complete",
"social-enum": "Enumerates social media",
"repo-enum": "Enumerates code repositories",
"subdomain-enum": "Enumerates subdomains",
"subdomain-hijack": "Detects hijackable subdomains",
"web-basic": "Basic, non-intrusive web scan functionality",
Expand Down
44 changes: 44 additions & 0 deletions bbot/core/helpers/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2052,6 +2052,50 @@ def human_to_bytes(filesize):
raise ValueError(f'Unable to convert filesize "{filesize}" to bytes')


def integer_to_ordinal(n):
"""
Convert an integer to its ordinal representation.

Args:
n (int): The integer to convert.

Returns:
str: The ordinal representation of the integer.

Examples:
>>> integer_to_ordinal(1)
'1st'
>>> integer_to_ordinal(2)
'2nd'
>>> integer_to_ordinal(3)
'3rd'
>>> integer_to_ordinal(11)
'11th'
>>> integer_to_ordinal(21)
'21st'
>>> integer_to_ordinal(101)
'101st'
"""
# Check the last digit
last_digit = n % 10
# Check the last two digits for special cases (11th, 12th, 13th)
last_two_digits = n % 100

if 10 <= last_two_digits <= 20:
suffix = "th"
else:
if last_digit == 1:
suffix = "st"
elif last_digit == 2:
suffix = "nd"
elif last_digit == 3:
suffix = "rd"
else:
suffix = "th"

return f"{n}{suffix}"


def cpu_architecture():
"""Return the CPU architecture of the current system.

Expand Down
3 changes: 2 additions & 1 deletion bbot/core/helpers/names_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
"rapid_unscheduled",
"raving",
"reckless",
"reductive",
"ripped",
"sadistic",
"satanic",
Expand Down Expand Up @@ -236,7 +237,6 @@
"ticklish",
"tiny",
"tricky",
"tufty",
"twitchy",
"ugly",
"unabated",
Expand Down Expand Up @@ -581,6 +581,7 @@
"rachel",
"radagast",
"ralph",
"rambunctious",
"randy",
"raymond",
"rebecca",
Expand Down
10 changes: 5 additions & 5 deletions bbot/core/helpers/web/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ async def curl(self, *args, **kwargs):
output = (await self.parent_helper.run(curl_command)).stdout
return output

def is_spider_danger(self, source_event, url):
def is_spider_danger(self, parent_event, url):
"""
Determines whether visiting a URL could potentially trigger a web-spider-like happening.

Expand All @@ -427,7 +427,7 @@ def is_spider_danger(self, source_event, url):
the function returns True, indicating a possible web-spider risk.

Args:
source_event: The source event object that discovered the URL.
parent_event: The parent event object that discovered the URL.
url (str): The URL to evaluate for web-spider risk.

Returns:
Expand All @@ -437,15 +437,15 @@ def is_spider_danger(self, source_event, url):
- Write tests for this function

Examples:
>>> is_spider_danger(source_event_obj, "https://example.com/subpage")
>>> is_spider_danger(parent_event_obj, "https://example.com/subpage")
True

>>> is_spider_danger(source_event_obj, "https://example.com/")
>>> is_spider_danger(parent_event_obj, "https://example.com/")
False
"""
url_depth = self.parent_helper.url_depth(url)
web_spider_depth = self.parent_helper.config.get("web_spider_depth", 1)
spider_distance = getattr(source_event, "web_spider_distance", 0) + 1
spider_distance = getattr(parent_event, "web_spider_distance", 0) + 1
web_spider_distance = self.parent_helper.config.get("web_spider_distance", 0)
if (url_depth > web_spider_depth) or (spider_distance > web_spider_distance):
return True
Expand Down
2 changes: 2 additions & 0 deletions bbot/modules/ajaxpro.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ async def handle_event(self, event):
},
"FINDING",
event,
context="{module} discovered Ajaxpro instance ({event.type}) at {event.data}",
)

elif event.type == "HTTP_RESPONSE":
Expand All @@ -53,4 +54,5 @@ async def handle_event(self, event):
},
"FINDING",
event,
context="{module} discovered Ajaxpro instance ({event.type}) at {event.data}",
)
7 changes: 5 additions & 2 deletions bbot/modules/azure_realm.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ async def handle_event(self, event):
auth_url = await self.getuserrealm(domain)
if auth_url:
url_event = self.make_event(
auth_url, "URL_UNVERIFIED", source=event, tags=["affiliate", "ms-auth-url"]
auth_url, "URL_UNVERIFIED", parent=event, tags=["affiliate", "ms-auth-url"]
)
url_event.source_domain = domain
await self.emit_event(url_event)
await self.emit_event(
url_event,
context="{module} queried login.microsoftonline.com for user realm and found {event.type}: {event.data}",
)

async def getuserrealm(self, domain):
url = f"https://login.microsoftonline.com/getuserrealm.srf?login=test@{domain}"
Expand Down
19 changes: 16 additions & 3 deletions bbot/modules/azure_tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,30 @@ async def handle_event(self, event):
self.verbose(f'Found {len(domains):,} domains under tenant for "{query}": {", ".join(sorted(domains))}')
for domain in domains:
if domain != query:
await self.emit_event(domain, "DNS_NAME", source=event, tags=["affiliate", "azure-tenant"])
await self.emit_event(
domain,
"DNS_NAME",
parent=event,
tags=["affiliate", "azure-tenant"],
context=f'{{module}} queried Outlook autodiscover for "{query}" and found {{event.type}}: {{event.data}}',
)
# tenant names
if domain.lower().endswith(".onmicrosoft.com"):
tenantname = domain.split(".")[0].lower()
if tenantname:
tenant_names.add(tenantname)

event_data = {"tenant-names": sorted(tenant_names), "domains": sorted(domains)}
tenant_names = sorted(tenant_names)
event_data = {"tenant-names": tenant_names, "domains": sorted(domains)}
tenant_names_str = ",".join(tenant_names)
if tenant_id is not None:
event_data["tenant-id"] = tenant_id
await self.emit_event(event_data, "AZURE_TENANT", source=event)
await self.emit_event(
event_data,
"AZURE_TENANT",
parent=event,
context=f'{{module}} queried Outlook autodiscover for "{query}" and found {{event.type}}: {tenant_names_str}',
)

async def query(self, domain):
url = f"{self.base_url}/autodiscover/autodiscover.svc"
Expand Down
18 changes: 15 additions & 3 deletions bbot/modules/baddns.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ async def handle_event(self, event):
"host": str(event.host),
}
await self.emit_event(
data, "VULNERABILITY", event, tags=[f"baddns-{module_instance.name.lower()}"]
data,
"VULNERABILITY",
event,
tags=[f"baddns-{module_instance.name.lower()}"],
context=f'{{module}}\'s "{r_dict["module"]}" module found {{event.type}}: {r_dict["description"]}',
)

elif r_dict["confidence"] in ["UNLIKELY", "POSSIBLE"] and not self.only_high_confidence:
Expand All @@ -75,7 +79,11 @@ async def handle_event(self, event):
"host": str(event.host),
}
await self.emit_event(
data, "FINDING", event, tags=[f"baddns-{module_instance.name.lower()}"]
data,
"FINDING",
event,
tags=[f"baddns-{module_instance.name.lower()}"],
context=f'{{module}}\'s "{r_dict["module"]}" module found {{event.type}}: {r_dict["description"]}',
)
else:
self.warning(f"Got unrecognized confidence level: {r['confidence']}")
Expand All @@ -84,5 +92,9 @@ async def handle_event(self, event):
if found_domains:
for found_domain in found_domains:
await self.emit_event(
found_domain, "DNS_NAME", event, tags=[f"baddns-{module_instance.name.lower()}"]
found_domain,
"DNS_NAME",
event,
tags=[f"baddns-{module_instance.name.lower()}"],
context=f'{{module}}\'s "{r_dict["module"]}" module found {{event.type}}: {{event.data}}',
)
2 changes: 1 addition & 1 deletion bbot/modules/baddns_zone.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,6 @@ def select_modules(self):

# minimize nsec records feeding back into themselves
async def filter_event(self, event):
if "baddns-nsec" in event.tags or "baddns-nsec" in event.source.tags:
if "baddns-nsec" in event.tags or "baddns-nsec" in event.parent.tags:
return False
return True
18 changes: 15 additions & 3 deletions bbot/modules/badsecrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,31 @@ async def handle_event(self, event):
"url": event.data["url"],
"host": str(event.host),
}
await self.emit_event(data, "VULNERABILITY", event)
await self.emit_event(
data,
"VULNERABILITY",
event,
context=f'{{module}}\'s "{r["detecting_module"]}" module found known {r["description"]["product"]} secret ({{event.type}}): "{r["secret"]}"',
)
elif r["type"] == "IdentifyOnly":
# There is little value to presenting a non-vulnerable asp.net viewstate, as it is not crackable without a Matrioshka brain. Just emit a technology instead.
if r["detecting_module"] == "ASPNET_Viewstate":
technology = "microsoft asp.net"
await self.emit_event(
{"technology": "microsoft asp.net", "url": event.data["url"], "host": str(event.host)},
{"technology": technology, "url": event.data["url"], "host": str(event.host)},
"TECHNOLOGY",
event,
context=f"{{module}} identified {{event.type}}: {technology}",
)
else:
data = {
"description": f"Cryptographic Product identified. Product Type: [{r['description']['product']}] Product: [{self.helpers.truncate_string(r['product'],2000)}] Detecting Module: [{r['detecting_module']}]",
"url": event.data["url"],
"host": str(event.host),
}
await self.emit_event(data, "FINDING", event)
await self.emit_event(
data,
"FINDING",
event,
context=f'{{module}} identified cryptographic product ({{event.type}}): "{r["description"]["product"]}"',
)
20 changes: 13 additions & 7 deletions bbot/modules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ class BaseModule:
batch_wait = 10
failed_request_abort_threshold = 5

default_discovery_context = "{module} discovered {event.type}: {event.data}"

_preserve_graph = False
_stats_exclude = False
_qsize = 1000
Expand Down Expand Up @@ -415,7 +417,7 @@ def make_event(self, *args, **kwargs):
raise_error (bool, optional): Whether to raise a validation error if the event could not be created. Defaults to False.

Examples:
>>> new_event = self.make_event("1.2.3.4", source=event)
>>> new_event = self.make_event("1.2.3.4", parent=event)
>>> await self.emit_event(new_event)

Returns:
Expand All @@ -425,15 +427,17 @@ def make_event(self, *args, **kwargs):
ValidationError: If the event could not be validated and raise_error is True.
"""
raise_error = kwargs.pop("raise_error", False)
module = kwargs.pop("module", None)
if module is None:
if (not args) or getattr(args[0], "module", None) is None:
kwargs["module"] = self
try:
event = self.scan.make_event(*args, **kwargs)
except ValidationError as e:
if raise_error:
raise
self.warning(f"{e}")
return
if not event.module:
event.module = self
return event

async def emit_event(self, *args, **kwargs):
Expand All @@ -454,9 +458,9 @@ async def emit_event(self, *args, **kwargs):
```

Examples:
>>> await self.emit_event("www.evilcorp.com", source=event, tags=["affiliate"])
>>> await self.emit_event("www.evilcorp.com", parent=event, tags=["affiliate"])

>>> new_event = self.make_event("1.2.3.4", source=event)
>>> new_event = self.make_event("1.2.3.4", parent=event)
>>> await self.emit_event(new_event)

Returns:
Expand Down Expand Up @@ -633,7 +637,8 @@ async def _worker(self):
else:
self.debug(f"Not accepting {event} because {reason}")
except asyncio.CancelledError:
self.log.trace("Worker cancelled")
# this trace was used for debugging leaked CancelledErrors from inside httpx
# self.log.trace("Worker cancelled")
raise
self.log.trace(f"Worker stopped")

Expand Down Expand Up @@ -1473,7 +1478,8 @@ async def _worker(self):
await self.forward_event(event, kwargs)

except asyncio.CancelledError:
self.log.trace("Worker cancelled")
# this trace was used for debugging leaked CancelledErrors from inside httpx
# self.log.trace("Worker cancelled")
raise
except BaseException as e:
self.critical(f"Critical failure in intercept module {self.name}: {e}")
Expand Down
14 changes: 12 additions & 2 deletions bbot/modules/bevigil.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,23 @@ async def handle_event(self, event):
subdomains = await self.query(query, request_fn=self.request_subdomains, parse_fn=self.parse_subdomains)
if subdomains:
for subdomain in subdomains:
await self.emit_event(subdomain, "DNS_NAME", source=event)
await self.emit_event(
subdomain,
"DNS_NAME",
parent=event,
context=f'{{module}} queried BeVigil\'s API for "{query}" and discovered {{event.type}}: {{event.data}}',
)

if self.urls:
urls = await self.query(query, request_fn=self.request_urls, parse_fn=self.parse_urls)
if urls:
for parsed_url in await self.helpers.run_in_executor_mp(self.helpers.validators.collapse_urls, urls):
await self.emit_event(parsed_url.geturl(), "URL_UNVERIFIED", source=event)
await self.emit_event(
parsed_url.geturl(),
"URL_UNVERIFIED",
parent=event,
context=f'{{module}} queried BeVigil\'s API for "{query}" and discovered {{event.type}}: {{event.data}}',
)

async def request_subdomains(self, query):
url = f"{self.base_url}/{self.helpers.quote(query)}/subdomains/"
Expand Down
9 changes: 8 additions & 1 deletion bbot/modules/bucket_file_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,14 @@ async def handle_aws(self, event):
bucket_file = url + "/" + key
file_extension = self.helpers.get_file_extension(key)
if file_extension not in self.scan.url_extension_blacklist:
await self.emit_event(bucket_file, "URL_UNVERIFIED", source=event, tags="filedownload")
extension_upper = file_extension.upper()
await self.emit_event(
bucket_file,
"URL_UNVERIFIED",
parent=event,
tags="filedownload",
context=f"{{module}} enumerate files in bucket and discovered {extension_upper} file at {{event.type}}: {{event.data}}",
)
urls_emitted += 1
if urls_emitted >= self.file_limit:
return
Loading
Loading