bbot 2.1.0.5004rc0__py3-none-any.whl → 2.1.0.5028rc0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bbot/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # version placeholder (replaced by poetry-dynamic-versioning)
2
- __version__ = "v2.1.0.5004rc"
2
+ __version__ = "v2.1.0.5028rc"
3
3
 
4
4
  from .scanner import Scanner, Preset
bbot/core/event/base.py CHANGED
@@ -25,6 +25,7 @@ from bbot.core.helpers import (
25
25
  is_domain,
26
26
  is_subdomain,
27
27
  is_ip,
28
+ is_ip_type,
28
29
  is_ptr,
29
30
  is_uri,
30
31
  url_depth,
@@ -157,7 +158,7 @@ class BaseEvent:
157
158
  Raises:
158
159
  ValidationError: If either `scan` or `parent` are not specified and `_dummy` is False.
159
160
  """
160
- self.uuid = uuid.uuid4()
161
+ self._uuid = uuid.uuid4()
161
162
  self._id = None
162
163
  self._hash = None
163
164
  self._data = None
@@ -352,6 +353,12 @@ class BaseEvent:
352
353
  return 80
353
354
  return self._port
354
355
 
356
+ @property
357
+ def netloc(self):
358
+ if self.host and is_ip_type(self.host, network=False):
359
+ return make_netloc(self.host, self.port)
360
+ return None
361
+
355
362
  @property
356
363
  def host_stem(self):
357
364
  """
@@ -449,6 +456,13 @@ class BaseEvent:
449
456
  self._id = f"{self.type}:{self.data_hash.hex()}"
450
457
  return self._id
451
458
 
459
+ @property
460
+ def uuid(self):
461
+ """
462
+ A universally unique identifier for the event
463
+ """
464
+ return f"{self.type}:{self._uuid}"
465
+
452
466
  @property
453
467
  def data_hash(self):
454
468
  """
@@ -741,7 +755,7 @@ class BaseEvent:
741
755
  """
742
756
  j = dict()
743
757
  # type, ID, scope description
744
- for i in ("type", "id", "uuid", "scope_description"):
758
+ for i in ("type", "id", "uuid", "scope_description", "netloc"):
745
759
  v = getattr(self, i, "")
746
760
  if v:
747
761
  j.update({i: str(v)})
@@ -760,6 +774,8 @@ class BaseEvent:
760
774
  j["host"] = str(self.host)
761
775
  j["resolved_hosts"] = sorted(str(h) for h in self.resolved_hosts)
762
776
  j["dns_children"] = {k: list(v) for k, v in self.dns_children.items()}
777
+ if isinstance(self.port, int):
778
+ j["port"] = self.port
763
779
  # web spider distance
764
780
  web_spider_distance = getattr(self, "web_spider_distance", None)
765
781
  if web_spider_distance is not None:
@@ -1709,7 +1725,7 @@ def event_from_json(j, siem_friendly=False):
1709
1725
  event = make_event(**kwargs)
1710
1726
  event_uuid = j.get("uuid", None)
1711
1727
  if event_uuid is not None:
1712
- event.uuid = uuid.UUID(event_uuid)
1728
+ event._uuid = uuid.UUID(event_uuid.split(":")[-1])
1713
1729
 
1714
1730
  resolved_hosts = j.get("resolved_hosts", [])
1715
1731
  event._resolved_hosts = set(resolved_hosts)
@@ -1721,7 +1737,8 @@ def event_from_json(j, siem_friendly=False):
1721
1737
  event._parent_id = parent_id
1722
1738
  parent_uuid = j.get("parent_uuid", None)
1723
1739
  if parent_uuid is not None:
1724
- event._parent_uuid = uuid.UUID(parent_uuid)
1740
+ parent_type, parent_uuid = parent_uuid.split(":", 1)
1741
+ event._parent_uuid = parent_type + ":" + str(uuid.UUID(parent_uuid))
1725
1742
  return event
1726
1743
  except KeyError as e:
1727
1744
  raise ValidationError(f"Event missing required field: {e}")
bbot/core/helpers/misc.py CHANGED
@@ -621,12 +621,13 @@ def is_ip(d, version=None):
621
621
  return False
622
622
 
623
623
 
624
- def is_ip_type(i):
624
+ def is_ip_type(i, network=None):
625
625
  """
626
626
  Checks if the given object is an instance of an IPv4 or IPv6 type from the ipaddress module.
627
627
 
628
628
  Args:
629
629
  i (ipaddress._BaseV4 or ipaddress._BaseV6): The IP object to check.
630
+ network (bool, optional): Whether to restrict the check to network types (IPv4Network or IPv6Network). Defaults to False.
630
631
 
631
632
  Returns:
632
633
  bool: True if the object is an instance of ipaddress._BaseV4 or ipaddress._BaseV6, False otherwise.
@@ -639,6 +640,12 @@ def is_ip_type(i):
639
640
  >>> is_ip_type("192.168.1.0/24")
640
641
  False
641
642
  """
643
+ if network is not None:
644
+ is_network = ipaddress._BaseNetwork in i.__class__.__mro__
645
+ if network:
646
+ return is_network
647
+ else:
648
+ return not is_network
642
649
  return ipaddress._IPAddressBase in i.__class__.__mro__
643
650
 
644
651
 
@@ -1260,7 +1267,7 @@ def gen_numbers(n, padding=2):
1260
1267
  return results
1261
1268
 
1262
1269
 
1263
- def make_netloc(host, port):
1270
+ def make_netloc(host, port=None):
1264
1271
  """Constructs a network location string from a given host and port.
1265
1272
 
1266
1273
  Args:
@@ -1289,7 +1296,7 @@ def make_netloc(host, port):
1289
1296
  if is_ip(host, version=6):
1290
1297
  host = f"[{host}]"
1291
1298
  if port is None:
1292
- return host
1299
+ return str(host)
1293
1300
  return f"{host}:{port}"
1294
1301
 
1295
1302
 
@@ -54,6 +54,9 @@ ptr_regex = re.compile(_ptr_regex)
54
54
  # uuid regex
55
55
  _uuid_regex = r"[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}"
56
56
  uuid_regex = re.compile(_uuid_regex, re.I)
57
+ # event uuid regex
58
+ _event_uuid_regex = r"[0-9A-Z_]+:[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}"
59
+ event_uuid_regex = re.compile(_event_uuid_regex, re.I)
57
60
 
58
61
  _open_port_regexes = (
59
62
  _dns_name_regex + r":[0-9]{1,5}",
bbot/modules/base.py CHANGED
@@ -984,8 +984,11 @@ class BaseModule:
984
984
  def _outgoing_dedup_hash(self, event):
985
985
  """
986
986
  Determines the criteria for what is considered to be a duplicate event if `suppress_dupes` is True.
987
+
988
+ We take into account the `internal` attribute we don't want an internal event (which isn't distributed to output modules)
989
+ to inadvertently suppress a non-internal event.
987
990
  """
988
- return hash((event, self.name))
991
+ return hash((event, self.name, event.internal, event.always_emit))
989
992
 
990
993
  def get_per_host_hash(self, event):
991
994
  """
@@ -137,10 +137,10 @@ class Generic_XXE(BaseSubmodule):
137
137
  post_body = f"""<?xml version="1.0" encoding="ISO-8859-1"?>
138
138
  <!DOCTYPE foo [
139
139
  <!ELEMENT foo ANY >
140
- <!ENTITY % {rand_entity} SYSTEM "http://{subdomain_tag}.{self.parent_module.interactsh_domain}" >
140
+ <!ENTITY {rand_entity} SYSTEM "http://{subdomain_tag}.{self.parent_module.interactsh_domain}" >
141
141
  ]>
142
142
  <foo>&{rand_entity};</foo>"""
143
- test_url = f"{event.parsed_url.scheme}://{event.parsed_url.netloc}/"
143
+ test_url = event.parsed_url.geturl()
144
144
  r = await self.parent_module.helpers.curl(
145
145
  url=test_url, method="POST", raw_body=post_body, headers={"Content-type": "application/xml"}
146
146
  )
@@ -16,12 +16,6 @@ class DNSResolve(BaseInterceptModule):
16
16
  _name = "host"
17
17
  _type = "internal"
18
18
 
19
- def _outgoing_dedup_hash(self, event):
20
- # this exists to ensure a second, more interesting host isn't passed up
21
- # because its ugly cousin spent its one dedup token before it arrived
22
- # by removing those race conditions, this makes for more consistent results
23
- return hash((event, self.name, event.always_emit))
24
-
25
19
  @property
26
20
  def module_threads(self):
27
21
  return self.dns_config.get("threads", 25)
@@ -876,6 +876,8 @@ class excavate(BaseInternalModule, BaseInterceptModule):
876
876
  yara_rules_combined = "\n".join(self.yara_rules_dict.values())
877
877
  try:
878
878
  self.info(f"Compiling {len(self.yara_rules_dict):,} YARA rules")
879
+ for rule_name, rule_content in self.yara_rules_dict.items():
880
+ self.debug(f" - {rule_name}")
879
881
  self.yara_rules = yara.compile(source=yara_rules_combined)
880
882
  except yara.SyntaxError as e:
881
883
  self.debug(yara_rules_combined)
@@ -112,15 +112,15 @@ class speculate(BaseInternalModule):
112
112
  speculate_open_ports = self.emit_open_ports and event_in_scope_distance
113
113
 
114
114
  # URL --> OPEN_TCP_PORT
115
- if event.type == "URL" or (event.type == "URL_UNVERIFIED" and self.open_port_consumers):
115
+ event_is_url = event.type == "URL"
116
+ if event_is_url or (event.type == "URL_UNVERIFIED" and self.open_port_consumers):
116
117
  # only speculate port from a URL if it wouldn't be speculated naturally from the host
117
118
  if event.host and (event.port not in self.ports or not speculate_open_ports):
118
119
  await self.emit_event(
119
120
  self.helpers.make_netloc(event.host, event.port),
120
121
  "OPEN_TCP_PORT",
121
122
  parent=event,
122
- internal=True,
123
- quick=(event.type == "URL"),
123
+ internal=not event_is_url, # if the URL is verified, the port is definitely open
124
124
  context=f"speculated {{event.type}} from {event.type}: {{event.data}}",
125
125
  )
126
126
 
@@ -169,7 +169,6 @@ class speculate(BaseInternalModule):
169
169
  "OPEN_TCP_PORT",
170
170
  parent=event,
171
171
  internal=True,
172
- quick=True,
173
172
  context="speculated {event.type}: {event.data}",
174
173
  )
175
174
 
@@ -16,13 +16,11 @@ class shodan_dns(shodan):
16
16
 
17
17
  base_url = "https://api.shodan.io"
18
18
 
19
- async def request_url(self, query):
20
- url = f"{self.base_url}/dns/domain/{self.helpers.quote(query)}?key={{api_key}}"
21
- response = await self.api_request(url)
22
- return response
19
+ async def handle_event(self, event):
20
+ await self.handle_event_paginated(event)
23
21
 
24
- def parse_results(self, r, query):
25
- json = r.json()
26
- if json:
27
- for hostname in json.get("subdomains", []):
28
- yield f"{hostname}.{query}"
22
+ def make_url(self, query):
23
+ return f"{self.base_url}/dns/domain/{self.helpers.quote(query)}?key={{api_key}}&page={{page}}"
24
+
25
+ def parse_results(self, json, query):
26
+ return [f"{sub}.{query}" for sub in json.get("subdomains", [])]
@@ -31,6 +31,11 @@ class subdomain_enum(BaseModule):
31
31
  # "lowest_parent": dedupe by lowest parent (lowest parent of www.api.test.evilcorp.com is api.test.evilcorp.com)
32
32
  dedup_strategy = "highest_parent"
33
33
 
34
+ # how many results to request per API call
35
+ page_size = 100
36
+ # arguments to pass to api_page_iter
37
+ api_page_iter_kwargs = {}
38
+
34
39
  @property
35
40
  def source_pretty_name(self):
36
41
  return f"{self.__class__.__name__} API"
@@ -61,10 +66,31 @@ class subdomain_enum(BaseModule):
61
66
  context=f'{{module}} searched {self.source_pretty_name} for "{query}" and found {{event.type}}: {{event.data}}',
62
67
  )
63
68
 
69
+ async def handle_event_paginated(self, event):
70
+ query = self.make_query(event)
71
+ async for result_batch in self.query_paginated(query):
72
+ for hostname in set(result_batch):
73
+ try:
74
+ hostname = self.helpers.validators.validate_host(hostname)
75
+ except ValueError as e:
76
+ self.verbose(e)
77
+ continue
78
+ if hostname and hostname.endswith(f".{query}") and not hostname == event.data:
79
+ await self.emit_event(
80
+ hostname,
81
+ "DNS_NAME",
82
+ event,
83
+ abort_if=self.abort_if,
84
+ context=f'{{module}} searched {self.source_pretty_name} for "{query}" and found {{event.type}}: {{event.data}}',
85
+ )
86
+
64
87
  async def request_url(self, query):
65
- url = f"{self.base_url}/subdomains/{self.helpers.quote(query)}"
88
+ url = self.make_url(query)
66
89
  return await self.api_request(url)
67
90
 
91
+ def make_url(self, query):
92
+ return f"{self.base_url}/subdomains/{self.helpers.quote(query)}"
93
+
68
94
  def make_query(self, event):
69
95
  query = event.data
70
96
  parents = list(self.helpers.domain_parents(event.data))
@@ -86,11 +112,11 @@ class subdomain_enum(BaseModule):
86
112
  for hostname in json:
87
113
  yield hostname
88
114
 
89
- async def query(self, query, parse_fn=None, request_fn=None):
90
- if parse_fn is None:
91
- parse_fn = self.parse_results
115
+ async def query(self, query, request_fn=None, parse_fn=None):
92
116
  if request_fn is None:
93
117
  request_fn = self.request_url
118
+ if parse_fn is None:
119
+ parse_fn = self.parse_results
94
120
  try:
95
121
  response = await request_fn(query)
96
122
  if response is None:
@@ -113,6 +139,19 @@ class subdomain_enum(BaseModule):
113
139
  except Exception as e:
114
140
  self.info(f"Error retrieving results for {query}: {e}", trace=True)
115
141
 
142
+ async def query_paginated(self, query):
143
+ url = self.make_url(query)
144
+ agen = self.api_page_iter(url, page_size=self.page_size, **self.api_page_iter_kwargs)
145
+ try:
146
+ async for response in agen:
147
+ subdomains = self.parse_results(response, query)
148
+ self.verbose(f'Got {len(subdomains):,} subdomains for "{query}"')
149
+ if not subdomains:
150
+ break
151
+ yield subdomains
152
+ finally:
153
+ agen.aclose()
154
+
116
155
  async def _is_wildcard(self, query):
117
156
  rdtypes = ("A", "AAAA", "CNAME")
118
157
  if self.helpers.is_dns_name(query):
bbot/modules/trickest.py CHANGED
@@ -28,39 +28,15 @@ class Trickest(subdomain_enum_apikey):
28
28
  return url, kwargs
29
29
 
30
30
  async def handle_event(self, event):
31
- query = self.make_query(event)
32
- async for result_batch in self.query(query):
33
- for hostname in set(result_batch):
34
- try:
35
- hostname = self.helpers.validators.validate_host(hostname)
36
- except ValueError as e:
37
- self.verbose(e)
38
- continue
39
- if hostname and hostname.endswith(f".{query}") and not hostname == event.data:
40
- await self.emit_event(
41
- hostname,
42
- "DNS_NAME",
43
- event,
44
- abort_if=self.abort_if,
45
- context=f'{{module}} searched {self.source_pretty_name} for "{query}" and found {{event.type}}: {{event.data}}',
46
- )
31
+ await self.handle_event_paginated(event)
47
32
 
48
- async def query(self, query):
33
+ def make_url(self, query):
49
34
  url = f"{self.base_url}/view?q=hostname%20~%20%22.{self.helpers.quote(query)}%22"
50
35
  url += f"&dataset_id={self.dataset_id}"
51
36
  url += "&limit={page_size}&offset={offset}&select=hostname&orderby=hostname"
52
- agen = self.api_page_iter(url, page_size=self.page_size)
53
- try:
54
- async for response in agen:
55
- subdomains = self.parse_results(response)
56
- self.verbose(f'Got {len(subdomains):,} subdomains for "{query}"')
57
- if not subdomains:
58
- break
59
- yield subdomains
60
- finally:
61
- agen.aclose()
37
+ return url
62
38
 
63
- def parse_results(self, j):
39
+ def parse_results(self, j, query):
64
40
  results = j.get("results", [])
65
41
  subdomains = set()
66
42
  for item in results:
@@ -15,6 +15,10 @@ class virustotal(subdomain_enum_apikey):
15
15
  options_desc = {"api_key": "VirusTotal API Key"}
16
16
 
17
17
  base_url = "https://www.virustotal.com/api/v3"
18
+ api_page_iter_kwargs = {"json": False, "next_key": lambda r: r.json().get("links", {}).get("next", "")}
19
+
20
+ def make_url(self, query):
21
+ return f"{self.base_url}/domains/{self.helpers.quote(query)}/subdomains"
18
22
 
19
23
  def prepare_api_request(self, url, kwargs):
20
24
  kwargs["headers"]["x-apikey"] = self.api_key
@@ -28,17 +32,3 @@ class virustotal(subdomain_enum_apikey):
28
32
  if match.endswith(query):
29
33
  results.add(match)
30
34
  return results
31
-
32
- async def query(self, query):
33
- results = set()
34
- url = f"{self.base_url}/domains/{self.helpers.quote(query)}/subdomains"
35
- agen = self.api_page_iter(url, json=False, next_key=lambda r: r.json().get("links", {}).get("next", ""))
36
- try:
37
- async for response in agen:
38
- r = self.parse_results(response, query)
39
- if not r:
40
- break
41
- results.update(r)
42
- finally:
43
- agen.aclose()
44
- return results
@@ -844,8 +844,6 @@ class Preset:
844
844
 
845
845
  if module in self.exclude_modules:
846
846
  reason = "the module has been excluded"
847
- if raise_error:
848
- raise ValidationError(f'Unable to add {module_type} module "{module}" because {reason}')
849
847
  return False, reason, {}
850
848
 
851
849
  module_flags = preloaded.get("flags", [])
bbot/scanner/scanner.py CHANGED
@@ -130,20 +130,22 @@ class Scanner:
130
130
  else:
131
131
  self.id = f"SCAN:{sha1(rand_string(20)).hexdigest()}"
132
132
 
133
- preset = kwargs.pop("preset", None)
133
+ custom_preset = kwargs.pop("preset", None)
134
134
  kwargs["_log"] = True
135
135
 
136
136
  from .preset import Preset
137
137
 
138
- if preset is None:
139
- preset = Preset(*targets, **kwargs)
140
- else:
141
- if not isinstance(preset, Preset):
142
- raise ValidationError(f'Preset must be of type Preset, not "{type(preset).__name__}"')
143
- self.preset = preset.bake(self)
138
+ base_preset = Preset(*targets, **kwargs)
139
+
140
+ if custom_preset is not None:
141
+ if not isinstance(custom_preset, Preset):
142
+ raise ValidationError(f'Preset must be of type Preset, not "{type(custom_preset).__name__}"')
143
+ base_preset.merge(custom_preset)
144
+
145
+ self.preset = base_preset.bake(self)
144
146
 
145
147
  # scan name
146
- if preset.scan_name is None:
148
+ if self.preset.scan_name is None:
147
149
  tries = 0
148
150
  while 1:
149
151
  if tries > 5:
@@ -158,7 +160,7 @@ class Scanner:
158
160
  break
159
161
  tries += 1
160
162
  else:
161
- scan_name = str(preset.scan_name)
163
+ scan_name = str(self.preset.scan_name)
162
164
  self.name = scan_name
163
165
 
164
166
  # make sure the preset has a description
@@ -166,8 +168,8 @@ class Scanner:
166
168
  self.preset.description = self.name
167
169
 
168
170
  # scan output dir
169
- if preset.output_dir is not None:
170
- self.home = Path(preset.output_dir).resolve() / self.name
171
+ if self.preset.output_dir is not None:
172
+ self.home = Path(self.preset.output_dir).resolve() / self.name
171
173
  else:
172
174
  self.home = self.preset.bbot_home / "scans" / self.name
173
175
 
@@ -351,14 +351,6 @@ async def test_cli_args(monkeypatch, caplog, capsys, clean_default_config):
351
351
  result = await cli._main()
352
352
  assert result == True
353
353
 
354
- # enable and exclude the same module
355
- caplog.clear()
356
- assert not caplog.text
357
- monkeypatch.setattr("sys.argv", ["bbot", "-m", "ffuf_shortnames", "-em", "ffuf_shortnames"])
358
- result = await cli._main()
359
- assert result == None
360
- assert 'Unable to add scan module "ffuf_shortnames" because the module has been excluded' in caplog.text
361
-
362
354
  # require flags
363
355
  monkeypatch.setattr("sys.argv", ["bbot", "-f", "active", "-rf", "passive"])
364
356
  result = await cli._main()
@@ -4,7 +4,7 @@ import ipaddress
4
4
 
5
5
  from ..bbot_fixtures import *
6
6
  from bbot.scanner import Scanner
7
- from bbot.core.helpers.regexes import uuid_regex
7
+ from bbot.core.helpers.regexes import event_uuid_regex
8
8
 
9
9
 
10
10
  @pytest.mark.asyncio
@@ -14,10 +14,19 @@ async def test_events(events, helpers):
14
14
  await scan._prep()
15
15
 
16
16
  assert events.ipv4.type == "IP_ADDRESS"
17
+ assert events.ipv4.netloc == "8.8.8.8"
18
+ assert events.ipv4.port is None
17
19
  assert events.ipv6.type == "IP_ADDRESS"
20
+ assert events.ipv6.netloc == "[2001:4860:4860::8888]"
21
+ assert events.ipv6.port is None
22
+ assert events.ipv6_open_port.netloc == "[2001:4860:4860::8888]:443"
18
23
  assert events.netv4.type == "IP_RANGE"
24
+ assert events.netv4.netloc is None
25
+ assert "netloc" not in events.netv4.json()
19
26
  assert events.netv6.type == "IP_RANGE"
20
27
  assert events.domain.type == "DNS_NAME"
28
+ assert events.domain.netloc == "publicapis.org"
29
+ assert events.domain.port is None
21
30
  assert "domain" in events.domain.tags
22
31
  assert events.subdomain.type == "DNS_NAME"
23
32
  assert "subdomain" in events.subdomain.tags
@@ -67,8 +76,14 @@ async def test_events(events, helpers):
67
76
  assert not events.netv6 in events.domain
68
77
  assert events.emoji not in events.domain
69
78
  assert events.domain not in events.emoji
70
- assert "evilcorp.com" == scan.make_event(" eViLcorp.COM.:88", "DNS_NAME", dummy=True)
71
- assert "evilcorp.com" == scan.make_event("evilcorp.com.", "DNS_NAME", dummy=True)
79
+ open_port_event = scan.make_event(" eViLcorp.COM.:88", "DNS_NAME", dummy=True)
80
+ dns_event = scan.make_event("evilcorp.com.", "DNS_NAME", dummy=True)
81
+ for e in (open_port_event, dns_event):
82
+ assert "evilcorp.com" == e
83
+ assert e.netloc == "evilcorp.com"
84
+ assert e.json()["netloc"] == "evilcorp.com"
85
+ assert e.port is None
86
+ assert "port" not in e.json()
72
87
 
73
88
  # url tests
74
89
  assert scan.make_event("http://evilcorp.com", dummy=True) == scan.make_event("http://evilcorp.com/", dummy=True)
@@ -78,8 +93,14 @@ async def test_events(events, helpers):
78
93
  assert "api.publicapis.org:443" in events.url_unverified
79
94
  assert "publicapis.org" not in events.url_unverified
80
95
  assert events.ipv4_url_unverified in events.ipv4
96
+ assert events.ipv4_url_unverified.netloc == "8.8.8.8:443"
97
+ assert events.ipv4_url_unverified.port == 443
98
+ assert events.ipv4_url_unverified.json()["port"] == 443
81
99
  assert events.ipv4_url_unverified in events.netv4
82
100
  assert events.ipv6_url_unverified in events.ipv6
101
+ assert events.ipv6_url_unverified.netloc == "[2001:4860:4860::8888]:443"
102
+ assert events.ipv6_url_unverified.port == 443
103
+ assert events.ipv6_url_unverified.json()["port"] == 443
83
104
  assert events.ipv6_url_unverified in events.netv6
84
105
  assert events.emoji not in events.url_unverified
85
106
  assert events.emoji not in events.ipv6_url_unverified
@@ -193,6 +214,8 @@ async def test_events(events, helpers):
193
214
 
194
215
  org_stub_1 = scan.make_event("STUB1", "ORG_STUB", parent=scan.root_event)
195
216
  org_stub_1.scope_distance == 1
217
+ assert org_stub_1.netloc == None
218
+ assert "netloc" not in org_stub_1.json()
196
219
  org_stub_2 = scan.make_event("STUB2", "ORG_STUB", parent=org_stub_1)
197
220
  org_stub_2.scope_distance == 2
198
221
 
@@ -420,11 +443,17 @@ async def test_events(events, helpers):
420
443
  parent_event2 = scan.make_event("evilcorp.com", parent=scan.root_event, context="test context")
421
444
 
422
445
  event1 = scan.make_event("evilcorp.com:80", parent=parent_event1, context="test context")
446
+ assert hasattr(event1, "_uuid")
423
447
  assert hasattr(event1, "uuid")
424
- assert isinstance(event1.uuid, uuid.UUID)
448
+ assert isinstance(event1._uuid, uuid.UUID)
449
+ assert isinstance(event1.uuid, str)
450
+ assert event1.uuid == f"{event1.type}:{event1._uuid}"
425
451
  event2 = scan.make_event("evilcorp.com:80", parent=parent_event2, context="test context")
452
+ assert hasattr(event2, "_uuid")
426
453
  assert hasattr(event2, "uuid")
427
- assert isinstance(event2.uuid, uuid.UUID)
454
+ assert isinstance(event2._uuid, uuid.UUID)
455
+ assert isinstance(event2.uuid, str)
456
+ assert event2.uuid == f"{event2.type}:{event2._uuid}"
428
457
  # ids should match because the event type + data is the same
429
458
  assert event1.id == event2.id
430
459
  # but uuids should be unique!
@@ -447,7 +476,7 @@ async def test_events(events, helpers):
447
476
  assert db_event.discovery_context == "test context"
448
477
  assert db_event.discovery_path == ["test context"]
449
478
  assert len(db_event.parent_chain) == 1
450
- assert all([uuid_regex.match(u) for u in db_event.parent_chain])
479
+ assert all([event_uuid_regex.match(u) for u in db_event.parent_chain])
451
480
  assert db_event.parent_chain[0] == str(db_event.uuid)
452
481
  assert db_event.parent.uuid == scan.root_event.uuid
453
482
  assert db_event.parent_uuid == scan.root_event.uuid
@@ -467,7 +496,7 @@ async def test_events(events, helpers):
467
496
  assert json_event["parent_chain"] == db_event.parent_chain
468
497
  assert json_event["parent_chain"][0] == str(db_event.uuid)
469
498
  reconstituted_event = event_from_json(json_event)
470
- assert isinstance(reconstituted_event.uuid, uuid.UUID)
499
+ assert isinstance(reconstituted_event._uuid, uuid.UUID)
471
500
  assert str(reconstituted_event.uuid) == json_event["uuid"]
472
501
  assert str(reconstituted_event.parent_uuid) == json_event["parent_uuid"]
473
502
  assert reconstituted_event.uuid == db_event.uuid
@@ -94,6 +94,15 @@ async def test_helpers_misc(helpers, scan, bbot_scanner, bbot_httpserver):
94
94
  ]
95
95
  assert helpers.is_ip("127.0.0.1")
96
96
  assert not helpers.is_ip("127.0.0.0.1")
97
+
98
+ assert not helpers.is_ip_type("127.0.0.1")
99
+ assert helpers.is_ip_type(ipaddress.ip_address("127.0.0.1"))
100
+ assert not helpers.is_ip_type(ipaddress.ip_address("127.0.0.1"), network=True)
101
+ assert helpers.is_ip_type(ipaddress.ip_address("127.0.0.1"), network=False)
102
+ assert helpers.is_ip_type(ipaddress.ip_network("127.0.0.0/8"))
103
+ assert helpers.is_ip_type(ipaddress.ip_network("127.0.0.0/8"), network=True)
104
+ assert not helpers.is_ip_type(ipaddress.ip_network("127.0.0.0/8"), network=False)
105
+
97
106
  assert helpers.is_dns_name("evilcorp.com")
98
107
  assert helpers.is_dns_name("evilcorp")
99
108
  assert not helpers.is_dns_name("evilcorp", include_local=False)
@@ -212,8 +221,12 @@ async def test_helpers_misc(helpers, scan, bbot_scanner, bbot_httpserver):
212
221
 
213
222
  ipv4_netloc = helpers.make_netloc("192.168.1.1", 80)
214
223
  assert ipv4_netloc == "192.168.1.1:80"
215
- ipv6_netloc = helpers.make_netloc("dead::beef", "443")
216
- assert ipv6_netloc == "[dead::beef]:443"
224
+ assert helpers.make_netloc("192.168.1.1") == "192.168.1.1"
225
+ assert helpers.make_netloc(ipaddress.ip_address("192.168.1.1"), None) == "192.168.1.1"
226
+ assert helpers.make_netloc("dead::beef", "443") == "[dead::beef]:443"
227
+ assert helpers.make_netloc(ipaddress.ip_address("dead::beef"), 443) == "[dead::beef]:443"
228
+ assert helpers.make_netloc("dead::beef", None) == "[dead::beef]"
229
+ assert helpers.make_netloc(ipaddress.ip_address("dead::beef"), None) == "[dead::beef]"
217
230
 
218
231
  assert helpers.get_file_extension("https://evilcorp.com/evilcorp.com/test/asdf.TXT") == "txt"
219
232
  assert helpers.get_file_extension("/etc/conf/test.tar.gz") == "gz"
@@ -575,7 +575,7 @@ async def test_manager_scope_accuracy(bbot_scanner, bbot_httpserver, bbot_other_
575
575
  },
576
576
  )
577
577
 
578
- assert len(events) == 10
578
+ assert len(events) == 12
579
579
  assert 1 == len([e for e in events if e.type == "IP_RANGE" and e.data == "127.0.0.110/31" and e.internal == False and e.scope_distance == 0])
580
580
  assert 0 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.110"])
581
581
  assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.111" and e.internal == False and e.scope_distance == 0])
@@ -589,9 +589,9 @@ async def test_manager_scope_accuracy(bbot_scanner, bbot_httpserver, bbot_other_
589
589
  assert 0 == len([e for e in events if e.type == "URL_UNVERIFIED" and e.data == "http://127.0.0.33:8889/"])
590
590
  assert 1 == len([e for e in events if e.type == "IP_ADDRESS" and e.data == "127.0.0.33" and e.internal == False and e.scope_distance == 0])
591
591
  assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8888"])
592
- assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8889"])
592
+ assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8889"])
593
593
  assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8888"])
594
- assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8889"])
594
+ assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8889"])
595
595
  assert 1 == len([e for e in events if e.type == "URL" and e.data == "http://127.0.0.222:8889/" and e.internal == False and e.scope_distance == 0])
596
596
  assert 0 == len([e for e in events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.222:8889"])
597
597
  assert 1 == len([e for e in events if e.type == "URL" and e.data == "http://127.0.0.33:8889/" and e.internal == False and e.scope_distance == 0])
@@ -603,7 +603,7 @@ async def test_manager_scope_accuracy(bbot_scanner, bbot_httpserver, bbot_other_
603
603
  assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.44:8888"])
604
604
  assert 0 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.55:8888"])
605
605
 
606
- assert len(all_events) == 29
606
+ assert len(all_events) == 31
607
607
  assert 1 == len([e for e in all_events if e.type == "IP_RANGE" and e.data == "127.0.0.110/31" and e.internal == False and e.scope_distance == 0])
608
608
  assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.110" and e.internal == True and e.scope_distance == 0])
609
609
  assert 2 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.111" and e.internal == False and e.scope_distance == 0])
@@ -618,8 +618,10 @@ async def test_manager_scope_accuracy(bbot_scanner, bbot_httpserver, bbot_other_
618
618
  assert 1 == len([e for e in all_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.33" and e.internal == False and e.scope_distance == 0])
619
619
  assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8888" and e.internal == True and e.scope_distance == 0])
620
620
  assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8889" and e.internal == True and e.scope_distance == 0])
621
+ assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8889" and e.internal == False and e.scope_distance == 0])
621
622
  assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8888" and e.internal == True and e.scope_distance == 0])
622
623
  assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8889" and e.internal == True and e.scope_distance == 0])
624
+ assert 1 == len([e for e in all_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8889" and e.internal == False and e.scope_distance == 0])
623
625
  assert 1 == len([e for e in all_events if e.type == "URL" and e.data == "http://127.0.0.222:8889/" and e.internal == False and e.scope_distance == 0])
624
626
  assert 1 == len([e for e in all_events if e.type == "HTTP_RESPONSE" and e.data["url"] == "http://127.0.0.222:8889/" and e.internal == False and e.scope_distance == 0])
625
627
  assert 1 == len([e for e in all_events if e.type == "URL" and e.data == "http://127.0.0.33:8889/" and e.internal == False and e.scope_distance == 0])
@@ -660,7 +662,7 @@ async def test_manager_scope_accuracy(bbot_scanner, bbot_httpserver, bbot_other_
660
662
  assert 1 == len([e for e in all_events_nodups if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.55:8888" and e.internal == True and e.scope_distance == 1])
661
663
 
662
664
  for _graph_output_events in (graph_output_events, graph_output_batch_events):
663
- assert len(_graph_output_events) == 10
665
+ assert len(_graph_output_events) == 12
664
666
  assert 1 == len([e for e in _graph_output_events if e.type == "IP_RANGE" and e.data == "127.0.0.110/31" and e.internal == False and e.scope_distance == 0])
665
667
  assert 0 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.110"])
666
668
  assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.111" and e.internal == False and e.scope_distance == 0])
@@ -674,9 +676,9 @@ async def test_manager_scope_accuracy(bbot_scanner, bbot_httpserver, bbot_other_
674
676
  assert 0 == len([e for e in _graph_output_events if e.type == "URL_UNVERIFIED" and e.data == "http://127.0.0.33:8889/"])
675
677
  assert 1 == len([e for e in _graph_output_events if e.type == "IP_ADDRESS" and e.data == "127.0.0.33"])
676
678
  assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8888"])
677
- assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8889"])
679
+ assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.222:8889"])
678
680
  assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8888"])
679
- assert 0 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8889"])
681
+ assert 1 == len([e for e in _graph_output_events if e.type == "OPEN_TCP_PORT" and e.data == "127.0.0.33:8889"])
680
682
  assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.data == "http://127.0.0.222:8889/" and e.internal == False and e.scope_distance == 0])
681
683
  assert 0 == len([e for e in _graph_output_events if e.type == "HTTP_RESPONSE" and e.data["input"] == "127.0.0.222:8889"])
682
684
  assert 1 == len([e for e in _graph_output_events if e.type == "URL" and e.data == "http://127.0.0.33:8889/" and e.internal == False and e.scope_distance == 0])
@@ -380,15 +380,14 @@ async def test_modules_basic_stats(helpers, events, bbot_scanner, httpx_mock, mo
380
380
  scan.modules["dummy"] = dummy(scan)
381
381
  events = [e async for e in scan.async_start()]
382
382
 
383
- assert len(events) == 10
384
- for e in events:
385
- log.critical(e)
383
+ assert len(events) == 11
386
384
  assert 2 == len([e for e in events if e.type == "SCAN"])
387
385
  assert 4 == len([e for e in events if e.type == "DNS_NAME"])
388
386
  # one from target and one from speculate
389
387
  assert 2 == len([e for e in events if e.type == "DNS_NAME" and e.data == "evilcorp.com"])
390
388
  assert 1 == len([e for e in events if e.type == "DNS_NAME" and e.data == "www.evilcorp.com"])
391
389
  assert 1 == len([e for e in events if e.type == "DNS_NAME" and e.data == "asdf.evilcorp.com"])
390
+ assert 1 == len([e for e in events if e.type == "OPEN_TCP_PORT" and e.data == "asdf.evilcorp.com:443"])
392
391
  assert 1 == len([e for e in events if e.type == "ORG_STUB" and e.data == "evilcorp"])
393
392
  assert 1 == len([e for e in events if e.type == "FINDING"])
394
393
  assert 1 == len([e for e in events if e.type == "URL_UNVERIFIED"])
@@ -397,6 +396,7 @@ async def test_modules_basic_stats(helpers, events, bbot_scanner, httpx_mock, mo
397
396
  "SCAN": 1,
398
397
  "DNS_NAME": 4,
399
398
  "URL": 1,
399
+ "OPEN_TCP_PORT": 1,
400
400
  "ORG_STUB": 1,
401
401
  "URL_UNVERIFIED": 1,
402
402
  "FINDING": 1,
@@ -431,16 +431,17 @@ async def test_modules_basic_stats(helpers, events, bbot_scanner, httpx_mock, mo
431
431
  assert python_stats.consumed == {
432
432
  "DNS_NAME": 4,
433
433
  "FINDING": 1,
434
+ "OPEN_TCP_PORT": 1,
434
435
  "ORG_STUB": 1,
435
436
  "SCAN": 1,
436
437
  "URL": 1,
437
438
  "URL_UNVERIFIED": 1,
438
439
  }
439
- assert python_stats.consumed_total == 9
440
+ assert python_stats.consumed_total == 10
440
441
 
441
442
  speculate_stats = scan.stats.module_stats["speculate"]
442
- assert speculate_stats.produced == {"DNS_NAME": 1, "URL_UNVERIFIED": 1, "ORG_STUB": 1}
443
- assert speculate_stats.produced_total == 3
443
+ assert speculate_stats.produced == {"DNS_NAME": 1, "URL_UNVERIFIED": 1, "ORG_STUB": 1, "OPEN_TCP_PORT": 1}
444
+ assert speculate_stats.produced_total == 4
444
445
  assert speculate_stats.consumed == {"URL": 1, "DNS_NAME": 3, "URL_UNVERIFIED": 1, "IP_ADDRESS": 3}
445
446
  assert speculate_stats.consumed_total == 8
446
447
 
@@ -169,6 +169,11 @@ exclude_flags:
169
169
 
170
170
  def test_preset_scope():
171
171
 
172
+ # test target merging
173
+ scan = Scanner("1.2.3.4", preset=Preset.from_dict({"target": ["evilcorp.com"]}))
174
+ assert set([str(h) for h in scan.preset.target.seeds.hosts]) == {"1.2.3.4", "evilcorp.com"}
175
+ assert set([e.data for e in scan.target]) == {"1.2.3.4", "evilcorp.com"}
176
+
172
177
  blank_preset = Preset()
173
178
  blank_preset = blank_preset.bake()
174
179
  assert not blank_preset.target
@@ -527,9 +532,22 @@ def test_preset_module_resolution(clean_default_config):
527
532
  assert set(preset.scan_modules) == {"wayback"}
528
533
 
529
534
  # modules + module exclusions
530
- with pytest.raises(ValidationError) as error:
531
- preset = Preset(exclude_modules=["sslcert"], modules=["sslcert", "wappalyzer", "wayback"]).bake()
532
- assert str(error.value) == 'Unable to add scan module "sslcert" because the module has been excluded'
535
+ preset = Preset(exclude_modules=["sslcert"], modules=["sslcert", "wappalyzer", "wayback"]).bake()
536
+ baked_preset = preset.bake()
537
+ assert baked_preset.modules == {
538
+ "wayback",
539
+ "cloudcheck",
540
+ "python",
541
+ "json",
542
+ "speculate",
543
+ "dnsresolve",
544
+ "aggregate",
545
+ "excavate",
546
+ "txt",
547
+ "httpx",
548
+ "csv",
549
+ "wappalyzer",
550
+ }
533
551
 
534
552
 
535
553
  @pytest.mark.asyncio
@@ -9,13 +9,32 @@ class TestShodan_DNS(ModuleTestBase):
9
9
  url="https://api.shodan.io/api-info?key=asdf",
10
10
  )
11
11
  module_test.httpx_mock.add_response(
12
- url="https://api.shodan.io/dns/domain/blacklanternsecurity.com?key=asdf",
12
+ url="https://api.shodan.io/dns/domain/blacklanternsecurity.com?key=asdf&page=1",
13
13
  json={
14
14
  "subdomains": [
15
15
  "asdf",
16
16
  ],
17
17
  },
18
18
  )
19
+ module_test.httpx_mock.add_response(
20
+ url="https://api.shodan.io/dns/domain/blacklanternsecurity.com?key=asdf&page=2",
21
+ json={
22
+ "subdomains": [
23
+ "www",
24
+ ],
25
+ },
26
+ )
27
+ await module_test.mock_dns(
28
+ {
29
+ "blacklanternsecurity.com": {
30
+ "A": ["127.0.0.11"],
31
+ },
32
+ "www.blacklanternsecurity.com": {"A": ["127.0.0.22"]},
33
+ "asdf.blacklanternsecurity.com": {"A": ["127.0.0.33"]},
34
+ }
35
+ )
19
36
 
20
37
  def check(self, module_test, events):
38
+ assert len([e for e in events if e.type == "DNS_NAME"]) == 3, "Failed to detect both subdomains"
21
39
  assert any(e.data == "asdf.blacklanternsecurity.com" for e in events), "Failed to detect subdomain"
40
+ assert any(e.data == "www.blacklanternsecurity.com" for e in events), "Failed to detect subdomain"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bbot
3
- Version: 2.1.0.5004rc0
3
+ Version: 2.1.0.5028rc0
4
4
  Summary: OSINT automation for hackers.
5
5
  Home-page: https://github.com/blacklanternsecurity/bbot
6
6
  License: GPL-3.0
@@ -1,4 +1,4 @@
1
- bbot/__init__.py,sha256=2MX0wg8qVd4uv2p2QWKxK-Lgomc0rk8etHVfyVf9jFo,130
1
+ bbot/__init__.py,sha256=uGNlVZLVuhzc5H4GbnGYmOTRrPsfNQvnztWI-cJlTjE,130
2
2
  bbot/cli.py,sha256=7S3a4eB-Dl8yodc5WC-927Z30CNlLl9EXimGvIVypJo,10434
3
3
  bbot/core/__init__.py,sha256=l255GJE_DvUnWvrRb0J5lG-iMztJ8zVvoweDOfegGtI,46
4
4
  bbot/core/config/__init__.py,sha256=zYNw2Me6tsEr8hOOkLb4BQ97GB7Kis2k--G81S8vofU,342
@@ -7,7 +7,7 @@ bbot/core/config/logger.py,sha256=C9txmKQtqSFnWU1AapwFS9cZgDBtNZQh4io_13d3AxA,95
7
7
  bbot/core/core.py,sha256=twd7-fiaaxzgcWTPwT1zbSWfAa_gHHfl7gAFvLYvFYg,6358
8
8
  bbot/core/engine.py,sha256=wGopKa2GNs61r16Pr_xtp6Si9AT6I-lE83iWhEgtxwA,29290
9
9
  bbot/core/event/__init__.py,sha256=8ut88ZUg0kbtWkOx2j3XzNr_3kTfgoM-3UdiWHFA_ag,56
10
- bbot/core/event/base.py,sha256=ZAz5jEd7sEX8Bl0EVdCpdOD7jXNjcdlqOJ83GpckCA8,58963
10
+ bbot/core/event/base.py,sha256=YHWevdDo5sHIIMKz_9TkWFYGTVtoD3fv957TpMO-6DQ,59498
11
11
  bbot/core/event/helpers.py,sha256=PUN4Trq5_wpKVuhmwUQWAr40apgMXhJ9Gz-VfZ0j3lA,1554
12
12
  bbot/core/flags.py,sha256=Ltvm8Bc4D65I55HuU5bzyjO1R3yMDNpVmreGU83ZBXE,1266
13
13
  bbot/core/helpers/__init__.py,sha256=0UNwcZjNsX41hbHdo3yZPuARkYWch-okI68DScexve4,86
@@ -28,13 +28,13 @@ bbot/core/helpers/dns/mock.py,sha256=Ztkp2aOuwDJ0NTQSlAk2H0s3Stx9wIM22Qm3VtqWMKM
28
28
  bbot/core/helpers/files.py,sha256=GqrwNGJljUvGSzaOW5-Y357hkt7j88dOYbzQxJGsdTc,5787
29
29
  bbot/core/helpers/helper.py,sha256=3O96peNBvSkaJosft8w9-nKjCscEdykTayGcUlHRqLw,8394
30
30
  bbot/core/helpers/interactsh.py,sha256=Q9IHUzH-T7e1s4YTHevHe-VJj1Mokv0EHY16UZJdl8M,12627
31
- bbot/core/helpers/misc.py,sha256=jhQsCAESXin5eBOQ5n8Uq5UhB4lcBjuBv3SEEaCaOEQ,86526
31
+ bbot/core/helpers/misc.py,sha256=rvfZmm8UHCChmbMorjPMybaCZTkERrKZhxvY9S4dVPc,86873
32
32
  bbot/core/helpers/names_generator.py,sha256=Sj_Q-7KQyElEpalzlUadSwaniESqrIVVEle9ycPIiho,10322
33
33
  bbot/core/helpers/ntlm.py,sha256=P2Xj4-GPos2iAzw4dfk0FJp6oGyycGhu2x6sLDVjYjs,2573
34
34
  bbot/core/helpers/process.py,sha256=6D9_LYZrhQ0Jb7Rn58rWMafmAZn7rVVA2LqMKwpR_xg,2271
35
35
  bbot/core/helpers/ratelimiter.py,sha256=K8qFIyJPJtfdb9kSW6_lL6ahWqxR2uWyCBkDlg6uJgo,1990
36
36
  bbot/core/helpers/regex.py,sha256=XURaY6ijpOYYU9lzWMAKg12G1VFtGJjlJl07_eN1xxk,4170
37
- bbot/core/helpers/regexes.py,sha256=BlbfziycPctQIvVSpD9dIz6HGyeNqRFnktCbwDRA56A,5686
37
+ bbot/core/helpers/regexes.py,sha256=kz3y3hyEJvsnF7-4NrkrHTwwhkbRqEYi_wqA_rmZlgQ,5859
38
38
  bbot/core/helpers/url.py,sha256=1NDrvirODzzD6Mcssu-4WDNerMeMdekHCFzhRCS0m3g,5947
39
39
  bbot/core/helpers/validators.py,sha256=cglRDybXiFDh2vKwqjqv7Ruu1TWmSzPcbuQ3Rvky5JU,9696
40
40
  bbot/core/helpers/web/__init__.py,sha256=pIEkL3DhjaGTSmZ7D3yKKYwWpntoLRILekV2wWsbsws,27
@@ -58,7 +58,7 @@ bbot/modules/baddns.py,sha256=FFGpJ0AdcpYBh_i8VMYOUj6C_PhzkFls263DoVmUXGY,6376
58
58
  bbot/modules/baddns_direct.py,sha256=_Z_snPDU5VqF3jb2CAoX5Sb94GnhYVP22JIK5TdpF_Y,3820
59
59
  bbot/modules/baddns_zone.py,sha256=bTpYzQYLt4ZJo_Jan5K4NsYL7O1PMVQvJHmYPy6QyvY,1035
60
60
  bbot/modules/badsecrets.py,sha256=WMeIhjafD63sXShYGtbAk2kw12PgxMIZh8EMbYg7x20,5108
61
- bbot/modules/base.py,sha256=HiffBnsNcCjKIgp_IHg82NAsXDXlnvoPs2wvx6HBZ6k,70893
61
+ bbot/modules/base.py,sha256=-nPgoPjMwcveZ81fzb4TwhbtBFqNYorj-zD3TG91ZPg,71115
62
62
  bbot/modules/bevigil.py,sha256=EFA4N_aJDF20KjrzbzdkHaTL1GfOF6ohBMlNIEJne6s,2850
63
63
  bbot/modules/binaryedge.py,sha256=W6VMbV7-tVAduUNbcmS6uLLJixb3sDJmqC9m4IGB6Yg,1391
64
64
  bbot/modules/bucket_amazon.py,sha256=mwjYeEAcdfOpjbOa1sD8U9KBMMVY_c8FoHjSGR9GQbg,730
@@ -96,7 +96,7 @@ bbot/modules/ffuf_shortnames.py,sha256=9Kh0kJsw7XXpXmCkiB5eAhG4h9rSo8Y-mB3p0EDa_
96
96
  bbot/modules/filedownload.py,sha256=1prC84wAQO-W1HstitKPQ0-eYEApjzFn3RHFa9oaqLc,8185
97
97
  bbot/modules/fingerprintx.py,sha256=rdlR9d64AntAhbS_eJzh8bZCeLPTJPSKdkdKdhH_qAo,3269
98
98
  bbot/modules/fullhunt.py,sha256=R70VpNZezUOeCOuYiYaG3oJCIOjOjB6lMPaVJ-xPVr0,1305
99
- bbot/modules/generic_ssrf.py,sha256=ZkaZuQE_sKlHH0uBUtJ45UKcGU91tdq8-n3ypneYdqA,7980
99
+ bbot/modules/generic_ssrf.py,sha256=FZ7XZ2RbBk5PVwa7wHasSkEsatUFWvCH70ZvPbg-EQA,7946
100
100
  bbot/modules/git.py,sha256=CMDarsmBemZEzZSeQTzB70XD8IRdwdG39zXpwDdgZbw,1383
101
101
  bbot/modules/git_clone.py,sha256=XFZXx0k97EMY3E5PZzdNvqQzZddOfRMaVp5ol2gk11s,2468
102
102
  bbot/modules/github_codesearch.py,sha256=VbzSsnJKL1SWSl6rB88qWl26bAgszM7Gbo4HCOl6XUU,3540
@@ -115,9 +115,9 @@ bbot/modules/internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
115
115
  bbot/modules/internal/aggregate.py,sha256=csWYIt2fUp9K_CRxP3bndUMIjpNIh8rmBubp5Fr1-nc,395
116
116
  bbot/modules/internal/base.py,sha256=BXO4Hc7XKaAOaLzolF3krJX1KibPxtek2GTQUgnCHk0,387
117
117
  bbot/modules/internal/cloudcheck.py,sha256=kkD5DC5Y7EPCiVTdEspZktkflArU3unx--9ciBikxHo,4099
118
- bbot/modules/internal/dnsresolve.py,sha256=IYIbtwvHzsVHsnBkgAmRUNMc3nnG5DuH4tBnq4Ja0FU,14923
119
- bbot/modules/internal/excavate.py,sha256=eQNorIZz0NHnsRYPt1MOR2Oj4Uc6---q4OSfr3ErNC4,51118
120
- bbot/modules/internal/speculate.py,sha256=diAI1_RGdcoKCBTiwsplReHNxL7S85OmBc8hsvr2yuA,9832
118
+ bbot/modules/internal/dnsresolve.py,sha256=rmXBb6r8MNYrHlnTbES1ok5M9q3JD3Oj6AOx6d0Bawc,14558
119
+ bbot/modules/internal/excavate.py,sha256=1kPiaUlpJVT_xgPgmzqhpf04uanIe9ZayQsWANbzhd4,51238
120
+ bbot/modules/internal/speculate.py,sha256=wP75lpdnxhoGiARmSdIlvPyYHQKx1Tu6oCu7_sKV9Qs,9850
121
121
  bbot/modules/internetdb.py,sha256=Edg0Z84dH8dPTZMd7RlzvYBYNq8JHs_ns_ldnFxwRKo,5415
122
122
  bbot/modules/ip2location.py,sha256=yGivX9fzvwvLpnqmYCP2a8SPjTarzrZxfRluog-nkME,2628
123
123
  bbot/modules/ipneighbor.py,sha256=Gr-HGtyZRDp_fPjpw-Mq1al7ocFdiZbKsAoAit-EUlA,1591
@@ -162,7 +162,7 @@ bbot/modules/robots.py,sha256=G2_IvLcI24iMXJxii44H1z6m5KWe2NMIDtVMbQGaXDE,2172
162
162
  bbot/modules/secretsdb.py,sha256=MA6IKo5rOvC0Dzt-F4QVrSwLkcPRwWLd9FpzqYkc8u8,3082
163
163
  bbot/modules/securitytrails.py,sha256=j2ZcY_qO48mQquWwS3FOaHgZiomr2ETMGv28y5qtRZM,1088
164
164
  bbot/modules/securitytxt.py,sha256=-GuIelHW04256VlcgfyV4ylfo7oSbAwToxtV2Cfo2p0,4558
165
- bbot/modules/shodan_dns.py,sha256=G0Wt8Uure-oY63n0ctRtQNzSGD6YFo3UnBz8Fjk4jJY,876
165
+ bbot/modules/shodan_dns.py,sha256=PJaeqGlTfhQfKgc_3fDmC4mLWsOb4BnRUpVD_h3Y8cU,836
166
166
  bbot/modules/sitedossier.py,sha256=0GOGMt0x3wro2FvP4ngNa2lcTmcjjq5x1SM8l5HG8Cs,2283
167
167
  bbot/modules/skymem.py,sha256=NApG68Eh22jMcV1H590SXvQIlLJY4JOfGUeNaOJaMKs,1928
168
168
  bbot/modules/smuggler.py,sha256=v8NCRgzd7wpEFZJUTAArG04bN8nNTGiHxYpGBapzi14,1580
@@ -175,15 +175,15 @@ bbot/modules/templates/bucket.py,sha256=x-c_iAeMILux6wRm0xkUUJkc2P69hYWS6DxqD7g5
175
175
  bbot/modules/templates/github.py,sha256=ENnDWpzzmZBsTisDx6Cg9V_NwJKyVyPIOpGAPktigdI,1455
176
176
  bbot/modules/templates/postman.py,sha256=oxwVusW2EdNotVX7xnnxCTnWtj3xNPbfs8aff9s4phs,614
177
177
  bbot/modules/templates/shodan.py,sha256=BfI0mNPbqkykGmjMtARhmCGKmk1uq7yTlZoPgzzJ040,1175
178
- bbot/modules/templates/subdomain_enum.py,sha256=W6UC3r2qkEryJAiwTz523G2STql3f8SDKGmlF79XQM0,6805
178
+ bbot/modules/templates/subdomain_enum.py,sha256=lT5MZF66OuzsyFFrj20wKlsZflzL9MOkPjDIbN3o65o,8375
179
179
  bbot/modules/templates/webhook.py,sha256=MYhKWrNYrsfM0a4PR6yVotudLyyCwgmy2eI-l9LvpBs,3706
180
- bbot/modules/trickest.py,sha256=dPUeUsOy0saUcFlpOyPXDh3ESKL2LIqH_oQkuAR4ToE,2657
180
+ bbot/modules/trickest.py,sha256=HfAzjnawxXd9ypi3gumDHqImE5-C7uwNugo8d_b9HT0,1544
181
181
  bbot/modules/trufflehog.py,sha256=KMFYbjKEyoNaJDoID0SoDvbCRPaDwalMD6H2lQaI1QE,8555
182
182
  bbot/modules/unstructured.py,sha256=si3_Y__A36QOBdkIUocVXCHrmUqM0E-JSnoOeRpELYE,5311
183
183
  bbot/modules/url_manipulation.py,sha256=BI-OhlzNzP5xvwzHphL4qdehc4NiEYnL2BNK-JoEm90,4322
184
184
  bbot/modules/urlscan.py,sha256=ajhiX2sj-zZDlKU1q5rE8JTzxioj1mDLqZ9PRSQCpAw,3741
185
185
  bbot/modules/viewdns.py,sha256=f0vwoLpua2Ovw1gcrjoafUdaAP9fi4bHgTUiDOe8iWg,2596
186
- bbot/modules/virustotal.py,sha256=hQZe1jgcC65fNHrYV98EMFQfi-j7qVyT4NAlfxsiEgc,1505
186
+ bbot/modules/virustotal.py,sha256=GsGaVF05IMgSNOQtUx1B8UXL5JA1Bt8M6ZDWJiiEQ1k,1213
187
187
  bbot/modules/wafw00f.py,sha256=I-jEnHWxO4Ga72ukdeBlTGJB9xeucCT3lpDhhFaVyAk,2536
188
188
  bbot/modules/wappalyzer.py,sha256=LL5QeY5DeG7LdaFzZZU-LXaVlJ-sHzOwQLgFtxW3TNg,2176
189
189
  bbot/modules/wayback.py,sha256=9cxd_HfHgLp4AChzA8C0Zjd6DIJ7c3NsJ02W2oLIXuU,3257
@@ -212,8 +212,8 @@ bbot/scanner/preset/args.py,sha256=9Nmir2dHJWzN66m6N-mA0QEKiOgt8vWq23O8BG50eMA,1
212
212
  bbot/scanner/preset/conditions.py,sha256=hFL9cSIWGEsv2TfM5UGurf0c91cyaM8egb5IngBmIjA,1569
213
213
  bbot/scanner/preset/environ.py,sha256=-wbFk1YHpU8IJLKVw23Q3btQTICeX0iulURo7D673L0,4732
214
214
  bbot/scanner/preset/path.py,sha256=p9tZC7XcgZv2jXpbEJAg1lU2b4ZLX5COFnCxEUOXz2g,2234
215
- bbot/scanner/preset/preset.py,sha256=nhAPjV-9P-udAs_CK5oOVuCeQP_c-a6Nem0SaNF98Ss,40216
216
- bbot/scanner/scanner.py,sha256=UyTpW1qTvegugUd2MWwtAE9CsAOs7bBmM8kMSqY7uCo,53583
215
+ bbot/scanner/preset/preset.py,sha256=7q6PB9LalIzHyb4eiMDVKE6CapWBCKVw7350M0fSiwM,40083
216
+ bbot/scanner/scanner.py,sha256=62DKCjgV1uLxNAwpxjvE5h1uzQCxG-nzBxp1PBCSVKc,53674
217
217
  bbot/scanner/stats.py,sha256=re93sArKXZSiD0Owgqk2J3Kdvfm3RL4Y9Qy_VOcaVk8,3623
218
218
  bbot/scanner/target.py,sha256=X25gpgRv5HmqQjGADiSe6b8744yOkRhAGAvKKYbXnSI,19886
219
219
  bbot/scripts/docs.py,sha256=kg2CzovmUVGJx9hBZjAjUdE1hXeIwC7Ry3CyrnE8GL8,10782
@@ -228,20 +228,20 @@ bbot/test/test_output.ndjson,sha256=Jfor8nUJ3QTEwXxD6UULrFXM4zhP5wflWo_UNekM3V8,
228
228
  bbot/test/test_step_1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
229
229
  bbot/test/test_step_1/test__module__tests.py,sha256=RpD4yuVuQRgbbUkfuasxUlyoVxhTm6TeDyi87y_AaK0,1461
230
230
  bbot/test/test_step_1/test_bloom_filter.py,sha256=OpiZXsBX-I8QdTK0LqSYkGMDLA6vL_6t0wco9ypxxtQ,2114
231
- bbot/test/test_step_1/test_cli.py,sha256=vJ_tERHP3-7PukHO0cfL_gEIKjHhAdTe4dsVR2yDct0,25039
231
+ bbot/test/test_step_1/test_cli.py,sha256=m4LyIiiedR41f5SpnlkxgE-f42fF7qoGcuthpYxQ_m8,24688
232
232
  bbot/test/test_step_1/test_command.py,sha256=5IeGV6TKB0xtFEsfsU_0mNrOmEdIQiQ3FHkUmsBNoOI,6485
233
233
  bbot/test/test_step_1/test_config.py,sha256=Q38hygpke2GDcv8OguVZuiSOnfDJxEMrRy20dN5Qsn0,887
234
234
  bbot/test/test_step_1/test_depsinstaller.py,sha256=zr9f-wJDotD1ZvKXGEuDRWzFYMAYBI6209mI_PWPtTQ,703
235
235
  bbot/test/test_step_1/test_dns.py,sha256=YZtSbja-Z76KC9MWBieRExolVWHm0WqssL0WHUpUiC8,30932
236
236
  bbot/test/test_step_1/test_docs.py,sha256=YWVGNRfzcrvDmFekX0Cq9gutQplsqvhKTpZ0XK4tWvo,82
237
237
  bbot/test/test_step_1/test_engine.py,sha256=Bfid3-D9ziN93w4vym97tFEn_l2Iof08wjITTv_lAZw,4269
238
- bbot/test/test_step_1/test_events.py,sha256=-nBbVfJdsEvx_W0X4aO9RpXGBZ61y1QwTjreDq494s8,43765
238
+ bbot/test/test_step_1/test_events.py,sha256=D9W3zGxRWUIm0SYklsWRE3IeAPcMdWLAOIMWkI24Rpc,45130
239
239
  bbot/test/test_step_1/test_files.py,sha256=5Q_3jPpMXULxDHsanSDUaj8zF8bXzKdiJZHOmoYpLhQ,699
240
- bbot/test/test_step_1/test_helpers.py,sha256=kRd64C_LD6VhBM1UJrWlkX1rGLFW0QtAj8mPX0dgbq4,37787
240
+ bbot/test/test_step_1/test_helpers.py,sha256=oY2hWhgL-TCB67ve1bAyIwZO3wNRWpx4SjCHNUxHep8,38676
241
241
  bbot/test/test_step_1/test_manager_deduplication.py,sha256=hZQpDXzg6zvzxFolVOcJuY-ME8NXjZUsqS70BRNXp8A,15594
242
- bbot/test/test_step_1/test_manager_scope_accuracy.py,sha256=5CptaGVplTLsGx7THjrJva99M9rumd7JCUFjTAXYUVE,79454
243
- bbot/test/test_step_1/test_modules_basic.py,sha256=JlO1waj6JArpazCUIYaNzOIv1qnKs-ArrfXw1D2B_tA,20155
244
- bbot/test/test_step_1/test_presets.py,sha256=SY-A3q3ubwFPJ0dSEgo9DHud-VpNrJa-PS-BGw2P_j8,36870
242
+ bbot/test/test_step_1/test_manager_scope_accuracy.py,sha256=_4O5bW9PA2DIeguvqzb4CMm0i1joqqBBppw7qElL2a0,79767
243
+ bbot/test/test_step_1/test_modules_basic.py,sha256=h4eCe-UhniwXZGhTEa6tH-RGeBF8tyjpSHAPsSu-ssw,20295
244
+ bbot/test/test_step_1/test_presets.py,sha256=RXdRCGBgwLBJk_npn5Pyph3IhLTfy0ULkb4v_aZjABA,37299
245
245
  bbot/test/test_step_1/test_python_api.py,sha256=BYIVREUy7rd7tKfUsnIX-w_eqQDTEo5UplMZeGMb4bs,5317
246
246
  bbot/test/test_step_1/test_regexes.py,sha256=btlDgwM5crSkXFZNydLYn8lCDmyLW_8oX_6Aos5xAHk,14376
247
247
  bbot/test/test_step_1/test_scan.py,sha256=yLQAZ5tvIwTULBGgUmXVg8KwMmujal7kB2p-S2_ORzA,5461
@@ -348,7 +348,7 @@ bbot/test/test_step_2/module_tests/test_module_robots.py,sha256=8rRw4GpGE6tN_W3o
348
348
  bbot/test/test_step_2/module_tests/test_module_secretsdb.py,sha256=EmqWGnqAQHGEZxbmJJXHwIgz5SQi4R3Fq982k6sYkZM,537
349
349
  bbot/test/test_step_2/module_tests/test_module_securitytrails.py,sha256=NB8_PhWN1-2s8wporRjI6rrQeQW4inoz4Z_mBhhycXo,758
350
350
  bbot/test/test_step_2/module_tests/test_module_securitytxt.py,sha256=i9Emp1utut1LPIe502Jb3XzN4GiVBeo4ATLFR9w4rbY,2382
351
- bbot/test/test_step_2/module_tests/test_module_shodan_dns.py,sha256=DQvs4e3_vtAfkzTCTHfFMh8UZHag6bUs_m6nbWL9rvM,713
351
+ bbot/test/test_step_2/module_tests/test_module_shodan_dns.py,sha256=9DpFizZguP2aFCKI1HgXzxy3sB_afA03pzIx0BDfgqg,1515
352
352
  bbot/test/test_step_2/module_tests/test_module_sitedossier.py,sha256=YAaTzCL-lOYFu-42zk4dsV33V3Da3Md6rcAuBy9jgeY,6584
353
353
  bbot/test/test_step_2/module_tests/test_module_skymem.py,sha256=VaRhEmrZ0auKmxExeuYmzryXpZ0h78AqSVozkqJ5dXo,2321
354
354
  bbot/test/test_step_2/module_tests/test_module_slack.py,sha256=oJvUzSowPAhpbMFnzl-iS3XvLQBxCaO_vofhu_bgR4w,317
@@ -393,8 +393,8 @@ bbot/wordlists/raft-small-extensions-lowercase_CLEANED.txt,sha256=ruUQwVfia1_m2u
393
393
  bbot/wordlists/top_open_ports_nmap.txt,sha256=LmdFYkfapSxn1pVuQC2LkOIY2hMLgG-Xts7DVtYzweM,42727
394
394
  bbot/wordlists/valid_url_schemes.txt,sha256=VciB-ww0y-O8Ii1wpTR6rJzGDiC2r-dhVsIJApS1ZYU,3309
395
395
  bbot/wordlists/wordninja_dns.txt.gz,sha256=DYHvvfW0TvzrVwyprqODAk4tGOxv5ezNmCPSdPuDUnQ,570241
396
- bbot-2.1.0.5004rc0.dist-info/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
397
- bbot-2.1.0.5004rc0.dist-info/METADATA,sha256=zye_cTeYT3jp0QSL3ThNRjBVFQVEHpADQQgJqjqWiow,16930
398
- bbot-2.1.0.5004rc0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
399
- bbot-2.1.0.5004rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
400
- bbot-2.1.0.5004rc0.dist-info/RECORD,,
396
+ bbot-2.1.0.5028rc0.dist-info/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
397
+ bbot-2.1.0.5028rc0.dist-info/METADATA,sha256=3Y7ocHDJJpm5Ebb7PhjejhASPdJO6tK52qlUo_6RUQI,16930
398
+ bbot-2.1.0.5028rc0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
399
+ bbot-2.1.0.5028rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
400
+ bbot-2.1.0.5028rc0.dist-info/RECORD,,