bbot 2.7.1.7212rc0__py3-none-any.whl → 2.7.2.7271rc0__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.

Potentially problematic release.


This version of bbot might be problematic. Click here for more details.

bbot/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  # version placeholder (replaced by poetry-dynamic-versioning)
2
- __version__ = "v2.7.1.7212rc"
2
+ __version__ = "v2.7.2.7271rc"
3
3
 
4
4
  from .scanner import Scanner, Preset
5
5
 
bbot/cli.py CHANGED
@@ -7,7 +7,7 @@ import multiprocessing
7
7
  from bbot.errors import *
8
8
  from bbot import __version__
9
9
  from bbot.logger import log_to_stderr
10
- from bbot.core.helpers.misc import chain_lists
10
+ from bbot.core.helpers.misc import chain_lists, rm_rf
11
11
 
12
12
 
13
13
  if multiprocessing.current_process().name == "MainProcess":
@@ -173,13 +173,27 @@ async def _main():
173
173
 
174
174
  # --install-all-deps
175
175
  if options.install_all_deps:
176
- all_modules = list(preset.module_loader.preloaded())
177
- scan.helpers.depsinstaller.force_deps = True
178
- succeeded, failed = await scan.helpers.depsinstaller.install(*all_modules)
179
- if failed:
180
- log.hugewarning(f"Failed to install dependencies for the following modules: {', '.join(failed)}")
176
+ preloaded_modules = preset.module_loader.preloaded()
177
+ scan_modules = [k for k, v in preloaded_modules.items() if str(v.get("type", "")) == "scan"]
178
+ output_modules = [k for k, v in preloaded_modules.items() if str(v.get("type", "")) == "output"]
179
+ log.verbose("Creating dummy scan with all modules + output modules for deps installation")
180
+ dummy_scan = Scanner(preset=preset, modules=scan_modules, output_modules=output_modules)
181
+ dummy_scan.helpers.depsinstaller.force_deps = True
182
+ log.info("Installing module dependencies")
183
+ await dummy_scan.load_modules()
184
+ log.verbose("Running module setups")
185
+ succeeded, hard_failed, soft_failed = await dummy_scan.setup_modules(deps_only=True)
186
+ # remove any leftovers from the dummy scan
187
+ rm_rf(dummy_scan.home, ignore_errors=True)
188
+ rm_rf(dummy_scan.temp_dir, ignore_errors=True)
189
+ if succeeded:
190
+ log.success(
191
+ f"Successfully installed dependencies for {len(succeeded):,} modules: {','.join(succeeded)}"
192
+ )
193
+ if soft_failed or hard_failed:
194
+ failed = soft_failed + hard_failed
195
+ log.warning(f"Failed to install dependencies for {len(failed):,} modules: {', '.join(failed)}")
181
196
  return False
182
- log.hugesuccess(f"Successfully installed dependencies for the following modules: {', '.join(succeeded)}")
183
197
  return True
184
198
 
185
199
  scan_name = str(scan.name)
bbot/core/flags.py CHANGED
@@ -6,6 +6,7 @@ flag_descriptions = {
6
6
  "cloud-enum": "Enumerates cloud resources",
7
7
  "code-enum": "Find public code repositories and search them for secrets etc.",
8
8
  "deadly": "Highly aggressive",
9
+ "download": "Modules that download files, apps, or repositories",
9
10
  "email-enum": "Enumerates email addresses",
10
11
  "iis-shortnames": "Scans for IIS Shortname vulnerability",
11
12
  "passive": "Never connects to target systems",
@@ -267,7 +267,8 @@ class WebHelper(EngineClient):
267
267
  if not path:
268
268
  raise WordlistError(f"Invalid wordlist: {path}")
269
269
  if "cache_hrs" not in kwargs:
270
- kwargs["cache_hrs"] = 720
270
+ # 4320 hrs = 180 days = 6 months
271
+ kwargs["cache_hrs"] = 4320
271
272
  if self.parent_helper.is_url(path):
272
273
  filename = await self.download(str(path), **kwargs)
273
274
  if filename is None:
bbot/core/modules.py CHANGED
@@ -56,7 +56,6 @@ class ModuleLoader:
56
56
  self._shared_deps = dict(SHARED_DEPS)
57
57
 
58
58
  self.__preloaded = {}
59
- self._modules = {}
60
59
  self._configs = {}
61
60
  self.flag_choices = set()
62
61
  self.all_module_choices = set()
@@ -463,7 +462,6 @@ class ModuleLoader:
463
462
  for module_name in module_names:
464
463
  module = self.load_module(module_name)
465
464
  modules[module_name] = module
466
- self._modules[module_name] = module
467
465
  return modules
468
466
 
469
467
  def load_module(self, module_name):
bbot/modules/apkpure.py CHANGED
@@ -6,7 +6,7 @@ from bbot.modules.base import BaseModule
6
6
  class apkpure(BaseModule):
7
7
  watched_events = ["MOBILE_APP"]
8
8
  produced_events = ["FILESYSTEM"]
9
- flags = ["passive", "safe", "code-enum"]
9
+ flags = ["passive", "safe", "code-enum", "download"]
10
10
  meta = {
11
11
  "description": "Download android applications from apkpure.com",
12
12
  "created_date": "2024-10-11",
bbot/modules/base.py CHANGED
@@ -213,6 +213,14 @@ class BaseModule:
213
213
 
214
214
  return True
215
215
 
216
+ async def setup_deps(self):
217
+ """
218
+ Similar to setup(), but reserved for installing dependencies not covered by Ansible.
219
+
220
+ This should always be used to install static dependencies like AI models, wordlists, etc.
221
+ """
222
+ return True
223
+
216
224
  async def handle_event(self, event, **kwargs):
217
225
  """Asynchronously handles incoming events that the module is configured to watch.
218
226
 
@@ -620,39 +628,26 @@ class BaseModule:
620
628
  name=f"{self.scan.name}.{self.name}._event_handler_watchdog()",
621
629
  )
622
630
 
623
- async def _setup(self):
624
- """
625
- Asynchronously sets up the module by invoking its 'setup()' method.
626
-
627
- This method catches exceptions during setup, sets the module's error state if necessary, and determines the
628
- status code based on the result of the setup process.
629
-
630
- Args:
631
- None
632
-
633
- Returns:
634
- tuple: A tuple containing the module's name, status (True for success, False for hard-fail, None for soft-fail),
635
- and an optional status message.
636
-
637
- Raises:
638
- Exception: Captured exceptions from the 'setup()' method are logged, but not propagated.
639
-
640
- Notes:
641
- - The 'setup()' method can return either a simple boolean status or a tuple of status and message.
642
- - A WordlistError exception triggers a soft-fail status.
643
- - The debug log will contain setup status information for the module.
644
- """
631
+ async def _setup(self, deps_only=False):
632
+ """ """
645
633
  status_codes = {False: "hard-fail", None: "soft-fail", True: "success"}
646
634
 
647
635
  status = False
648
636
  self.debug(f"Setting up module {self.name}")
649
637
  try:
650
- result = await self.setup()
651
- if type(result) == tuple and len(result) == 2:
652
- status, msg = result
653
- else:
654
- status = result
655
- msg = status_codes[status]
638
+ funcs = [self.setup_deps]
639
+ if not deps_only:
640
+ funcs.append(self.setup)
641
+ for func in funcs:
642
+ self.debug(f"Running {self.name}.{func.__name__}()")
643
+ result = await func()
644
+ if type(result) == tuple and len(result) == 2:
645
+ status, msg = result
646
+ else:
647
+ status = result
648
+ msg = status_codes[status]
649
+ if status is False:
650
+ break
656
651
  self.debug(f"Finished setting up module {self.name}")
657
652
  except Exception as e:
658
653
  self.set_error_state(f"Unexpected error during module setup: {e}", critical=True)
bbot/modules/dnsbrute.py CHANGED
@@ -23,9 +23,14 @@ class dnsbrute(subdomain_enum):
23
23
  dedup_strategy = "lowest_parent"
24
24
  _qsize = 10000
25
25
 
26
+ async def setup_deps(self):
27
+ self.subdomain_file = await self.helpers.wordlist(self.config.get("wordlist"))
28
+ # tell the dnsbrute helper to fetch the resolver file
29
+ await self.helpers.dns.brute.resolver_file()
30
+ return True
31
+
26
32
  async def setup(self):
27
33
  self.max_depth = max(1, self.config.get("max_depth", 5))
28
- self.subdomain_file = await self.helpers.wordlist(self.config.get("wordlist"))
29
34
  self.subdomain_list = set(self.helpers.read_file(self.subdomain_file))
30
35
  self.wordlist_size = len(self.subdomain_list)
31
36
  return await super().setup()
@@ -8,7 +8,7 @@ from bbot.modules.base import BaseModule
8
8
  class docker_pull(BaseModule):
9
9
  watched_events = ["CODE_REPOSITORY"]
10
10
  produced_events = ["FILESYSTEM"]
11
- flags = ["passive", "safe", "slow", "code-enum"]
11
+ flags = ["passive", "safe", "slow", "code-enum", "download"]
12
12
  meta = {
13
13
  "description": "Download images from a docker repository",
14
14
  "created_date": "2024-03-24",
bbot/modules/ffuf.py CHANGED
@@ -37,12 +37,15 @@ class ffuf(BaseModule):
37
37
 
38
38
  in_scope_only = True
39
39
 
40
+ async def setup_deps(self):
41
+ self.wordlist = await self.helpers.wordlist(self.config.get("wordlist"))
42
+ return True
43
+
40
44
  async def setup(self):
41
45
  self.proxy = self.scan.web_config.get("http_proxy", "")
42
46
  self.canary = "".join(random.choice(string.ascii_lowercase) for i in range(10))
43
47
  wordlist_url = self.config.get("wordlist", "")
44
48
  self.debug(f"Using wordlist [{wordlist_url}]")
45
- self.wordlist = await self.helpers.wordlist(wordlist_url)
46
49
  self.wordlist_lines = self.generate_wordlist(self.wordlist)
47
50
  self.tempfile, tempfile_len = self.generate_templist()
48
51
  self.rate = self.config.get("rate", 0)
@@ -87,14 +87,17 @@ class ffuf_shortnames(ffuf):
87
87
  found_prefixes.add(prefix)
88
88
  return list(found_prefixes)
89
89
 
90
- async def setup(self):
91
- self.proxy = self.scan.web_config.get("http_proxy", "")
92
- self.canary = "".join(random.choice(string.ascii_lowercase) for i in range(10))
90
+ async def setup_deps(self):
93
91
  wordlist_extensions = self.config.get("wordlist_extensions", "")
94
92
  if not wordlist_extensions:
95
93
  wordlist_extensions = f"{self.helpers.wordlist_dir}/raft-small-extensions-lowercase_CLEANED.txt"
96
94
  self.debug(f"Using [{wordlist_extensions}] for shortname candidate extension list")
97
95
  self.wordlist_extensions = await self.helpers.wordlist(wordlist_extensions)
96
+ return True
97
+
98
+ async def setup(self):
99
+ self.proxy = self.scan.web_config.get("http_proxy", "")
100
+ self.canary = "".join(random.choice(string.ascii_lowercase) for i in range(10))
98
101
  self.ignore_redirects = self.config.get("ignore_redirects")
99
102
  self.max_predictions = self.config.get("max_predictions")
100
103
  self.find_subwords = self.config.get("find_subwords")
@@ -14,7 +14,7 @@ class filedownload(BaseModule):
14
14
 
15
15
  watched_events = ["URL_UNVERIFIED", "HTTP_RESPONSE"]
16
16
  produced_events = ["FILESYSTEM"]
17
- flags = ["active", "safe", "web-basic"]
17
+ flags = ["active", "safe", "web-basic", "download"]
18
18
  meta = {
19
19
  "description": "Download common filetypes such as PDF, DOCX, PPTX, etc.",
20
20
  "created_date": "2023-10-11",
@@ -94,6 +94,12 @@ class filedownload(BaseModule):
94
94
 
95
95
  scope_distance_modifier = 3
96
96
 
97
+ async def setup_deps(self):
98
+ self.mime_db_file = await self.helpers.wordlist(
99
+ "https://raw.githubusercontent.com/jshttp/mime-db/master/db.json"
100
+ )
101
+ return True
102
+
97
103
  async def setup(self):
98
104
  self.extensions = list({e.lower().strip(".") for e in self.config.get("extensions", [])})
99
105
  self.max_filesize = self.config.get("max_filesize", "10MB")
@@ -105,9 +111,6 @@ class filedownload(BaseModule):
105
111
  else:
106
112
  self.download_dir = self.scan.temp_dir / "filedownload"
107
113
  self.helpers.mkdir(self.download_dir)
108
- self.mime_db_file = await self.helpers.wordlist(
109
- "https://raw.githubusercontent.com/jshttp/mime-db/master/db.json"
110
- )
111
114
  self.mime_db = {}
112
115
  with open(self.mime_db_file) as f:
113
116
  mime_db = json.load(f)
bbot/modules/git_clone.py CHANGED
@@ -6,7 +6,7 @@ from bbot.modules.templates.github import github
6
6
  class git_clone(github):
7
7
  watched_events = ["CODE_REPOSITORY"]
8
8
  produced_events = ["FILESYSTEM"]
9
- flags = ["passive", "safe", "slow", "code-enum"]
9
+ flags = ["passive", "safe", "slow", "code-enum", "download"]
10
10
  meta = {
11
11
  "description": "Clone code github repositories",
12
12
  "created_date": "2024-03-08",
bbot/modules/gitdumper.py CHANGED
@@ -7,7 +7,7 @@ from bbot.modules.base import BaseModule
7
7
  class gitdumper(BaseModule):
8
8
  watched_events = ["CODE_REPOSITORY"]
9
9
  produced_events = ["FILESYSTEM"]
10
- flags = ["passive", "safe", "slow", "code-enum"]
10
+ flags = ["passive", "safe", "slow", "code-enum", "download"]
11
11
  meta = {
12
12
  "description": "Download a leaked .git folder recursively or by fuzzing common names",
13
13
  "created_date": "2025-02-11",
@@ -8,7 +8,7 @@ from bbot.modules.templates.github import github
8
8
  class github_workflows(github):
9
9
  watched_events = ["CODE_REPOSITORY"]
10
10
  produced_events = ["FILESYSTEM"]
11
- flags = ["passive", "safe", "code-enum"]
11
+ flags = ["passive", "safe", "code-enum", "download"]
12
12
  meta = {
13
13
  "description": "Download a github repositories workflow logs and workflow artifacts",
14
14
  "created_date": "2024-04-29",
@@ -0,0 +1,31 @@
1
+ from bbot.modules.templates.gitlab import GitLabBaseModule
2
+
3
+
4
+ class gitlab_com(GitLabBaseModule):
5
+ watched_events = ["SOCIAL"]
6
+ produced_events = [
7
+ "CODE_REPOSITORY",
8
+ ]
9
+ flags = ["active", "safe", "code-enum"]
10
+ meta = {
11
+ "description": "Enumerate GitLab SaaS (gitlab.com/org) for projects and groups",
12
+ "created_date": "2024-03-11",
13
+ "author": "@TheTechromancer",
14
+ }
15
+
16
+ options = {"api_key": ""}
17
+ options_desc = {"api_key": "GitLab access token (for gitlab.com/org only)"}
18
+
19
+ # This is needed because we are consuming SOCIAL events, which aren't in scope
20
+ scope_distance_modifier = 2
21
+
22
+ async def handle_event(self, event):
23
+ await self.handle_social(event)
24
+
25
+ async def filter_event(self, event):
26
+ if event.data["platform"] != "gitlab":
27
+ return False, "platform is not gitlab"
28
+ _, domain = self.helpers.split_domain(event.host)
29
+ if domain not in self.saas_domains:
30
+ return False, "gitlab instance is not gitlab.com/org"
31
+ return True
@@ -0,0 +1,84 @@
1
+ from bbot.modules.templates.gitlab import GitLabBaseModule
2
+
3
+
4
+ class gitlab_onprem(GitLabBaseModule):
5
+ watched_events = ["HTTP_RESPONSE", "TECHNOLOGY", "SOCIAL"]
6
+ produced_events = [
7
+ "TECHNOLOGY",
8
+ "SOCIAL",
9
+ "CODE_REPOSITORY",
10
+ "FINDING",
11
+ ]
12
+ flags = ["active", "safe", "code-enum"]
13
+ meta = {
14
+ "description": "Detect self-hosted GitLab instances and query them for repositories",
15
+ "created_date": "2024-03-11",
16
+ "author": "@TheTechromancer",
17
+ }
18
+
19
+ # Optional GitLab access token (only required for gitlab.com, but still
20
+ # supported for on-prem installations that expose private projects).
21
+ options = {"api_key": ""}
22
+ options_desc = {"api_key": "GitLab access token (for self-hosted instances only)"}
23
+
24
+ # Allow accepting events slightly beyond configured max distance so we can
25
+ # discover repos on neighbouring infrastructure.
26
+ scope_distance_modifier = 2
27
+
28
+ async def handle_event(self, event):
29
+ if event.type == "HTTP_RESPONSE":
30
+ await self.handle_http_response(event)
31
+ elif event.type == "TECHNOLOGY":
32
+ await self.handle_technology(event)
33
+ elif event.type == "SOCIAL":
34
+ await self.handle_social(event)
35
+
36
+ async def filter_event(self, event):
37
+ # only accept out-of-scope SOCIAL events
38
+ if event.type == "HTTP_RESPONSE":
39
+ if event.scope_distance > self.scan.scope_search_distance:
40
+ return False, "event is out of scope distance"
41
+ elif event.type == "TECHNOLOGY":
42
+ if not event.data["technology"].lower().startswith("gitlab"):
43
+ return False, "technology is not gitlab"
44
+ if not self.helpers.is_ip(event.host) and self.helpers.tldextract(event.host).domain == "gitlab":
45
+ return False, "gitlab instance is not self-hosted"
46
+ elif event.type == "SOCIAL":
47
+ if event.data["platform"] != "gitlab":
48
+ return False, "platform is not gitlab"
49
+ _, domain = self.helpers.split_domain(event.host)
50
+ if domain in self.saas_domains:
51
+ return False, "gitlab instance is not self-hosted"
52
+ return True
53
+
54
+ async def handle_http_response(self, event):
55
+ """Identify GitLab servers from HTTP responses."""
56
+ headers = event.data.get("header", {})
57
+ if "x_gitlab_meta" in headers:
58
+ url = event.parsed_url._replace(path="/").geturl()
59
+ await self.emit_event(
60
+ {"host": str(event.host), "technology": "GitLab", "url": url},
61
+ "TECHNOLOGY",
62
+ parent=event,
63
+ context=f"{{module}} detected {{event.type}}: GitLab at {url}",
64
+ )
65
+ description = f"GitLab server at {event.host}"
66
+ await self.emit_event(
67
+ {"host": str(event.host), "description": description},
68
+ "FINDING",
69
+ parent=event,
70
+ context=f"{{module}} detected {{event.type}}: {description}",
71
+ )
72
+
73
+ async def handle_technology(self, event):
74
+ """Enumerate projects & groups once we know a host is GitLab."""
75
+ base_url = self.get_base_url(event)
76
+
77
+ # Projects owned by the authenticated user (or public projects if no
78
+ # authentication).
79
+ projects_url = self.helpers.urljoin(base_url, "api/v4/projects?simple=true")
80
+ await self.handle_projects_url(projects_url, event)
81
+
82
+ # Group enumeration.
83
+ groups_url = self.helpers.urljoin(base_url, "api/v4/groups?simple=true")
84
+ await self.handle_groups_url(groups_url, event)
bbot/modules/medusa.py CHANGED
@@ -1,6 +1,5 @@
1
1
  import re
2
2
  from bbot.modules.base import BaseModule
3
- from bbot.errors import WordlistError
4
3
 
5
4
 
6
5
  class medusa(BaseModule):
@@ -102,13 +101,11 @@ class medusa(BaseModule):
102
101
  },
103
102
  ]
104
103
 
105
- async def setup(self):
106
- # Try to cache wordlist
107
- try:
108
- self.snmp_wordlist_path = await self.helpers.wordlist(self.config.get("snmp_wordlist"))
109
- except WordlistError as e:
110
- return False, f"Error retrieving wordlist: {e}"
104
+ async def setup_deps(self):
105
+ self.snmp_wordlist_path = await self.helpers.wordlist(self.config.get("snmp_wordlist"))
106
+ return True
111
107
 
108
+ async def setup(self):
112
109
  self.password_match_regex = re.compile(r"Password:\s*(\S+)")
113
110
  self.success_indicator_match_regex = re.compile(r"\[([^\]]+)\]\s*$")
114
111
 
@@ -82,18 +82,21 @@ class paramminer_headers(BaseModule):
82
82
 
83
83
  header_regex = re.compile(r"^[!#$%&\'*+\-.^_`|~0-9a-zA-Z]+: [^\r\n]+$")
84
84
 
85
- async def setup(self):
86
- self.recycle_words = self.config.get("recycle_words", True)
87
- self.event_dict = {}
88
- self.already_checked = set()
85
+ async def setup_deps(self):
89
86
  wordlist = self.config.get("wordlist", "")
90
87
  if not wordlist:
91
88
  wordlist = f"{self.helpers.wordlist_dir}/{self.default_wordlist}"
89
+ self.wordlist_file = await self.helpers.wordlist(wordlist)
92
90
  self.debug(f"Using wordlist: [{wordlist}]")
91
+ return True
92
+
93
+ async def setup(self):
94
+ self.recycle_words = self.config.get("recycle_words", True)
95
+ self.event_dict = {}
96
+ self.already_checked = set()
97
+
93
98
  self.wl = {
94
- h.strip().lower()
95
- for h in self.helpers.read_file(await self.helpers.wordlist(wordlist))
96
- if len(h) > 0 and "%" not in h
99
+ h.strip().lower() for h in self.helpers.read_file(self.wordlist_file) if len(h) > 0 and "%" not in h
97
100
  }
98
101
 
99
102
  # check against the boring list (if the option is set)
@@ -7,7 +7,7 @@ from bbot.modules.templates.postman import postman
7
7
  class postman_download(postman):
8
8
  watched_events = ["CODE_REPOSITORY"]
9
9
  produced_events = ["FILESYSTEM"]
10
- flags = ["passive", "subdomain-enum", "safe", "code-enum"]
10
+ flags = ["passive", "subdomain-enum", "safe", "code-enum", "download"]
11
11
  meta = {
12
12
  "description": "Download workspaces, collections, requests from Postman",
13
13
  "created_date": "2024-09-07",
@@ -0,0 +1,98 @@
1
+ from bbot.modules.base import BaseModule
2
+
3
+
4
+ class GitLabBaseModule(BaseModule):
5
+ """Common functionality for interacting with GitLab instances.
6
+
7
+ This template is intended to be inherited by two concrete modules:
8
+ 1. ``gitlab_com`` – Handles public SaaS instances (gitlab.com / gitlab.org).
9
+ 2. ``gitlab_onprem`` – Handles self-hosted, on-premises GitLab servers.
10
+
11
+ Both child modules share identical behaviour when talking to the GitLab
12
+ REST API; they only differ in which events they are willing to accept.
13
+ """
14
+
15
+ # domains owned by GitLab
16
+ saas_domains = ["gitlab.com", "gitlab.org"]
17
+
18
+ async def setup(self):
19
+ if self.options.get("api_key") is not None:
20
+ await self.require_api_key()
21
+ return True
22
+
23
+ async def handle_social(self, event):
24
+ """Enumerate projects belonging to a user or group profile."""
25
+ username = event.data.get("profile_name", "")
26
+ if not username:
27
+ return
28
+ base_url = self.get_base_url(event)
29
+ urls = [
30
+ # User-owned projects
31
+ self.helpers.urljoin(base_url, f"api/v4/users/{username}/projects?simple=true"),
32
+ # Group-owned projects
33
+ self.helpers.urljoin(base_url, f"api/v4/groups/{username}/projects?simple=true"),
34
+ ]
35
+ for url in urls:
36
+ await self.handle_projects_url(url, event)
37
+
38
+ async def handle_projects_url(self, projects_url, event):
39
+ for project in await self.gitlab_json_request(projects_url):
40
+ project_url = project.get("web_url", "")
41
+ if project_url:
42
+ code_event = self.make_event({"url": project_url}, "CODE_REPOSITORY", tags="git", parent=event)
43
+ await self.emit_event(
44
+ code_event,
45
+ context=f"{{module}} enumerated projects and found {{event.type}} at {project_url}",
46
+ )
47
+ namespace = project.get("namespace", {})
48
+ if namespace:
49
+ await self.handle_namespace(namespace, event)
50
+
51
+ async def handle_groups_url(self, groups_url, event):
52
+ for group in await self.gitlab_json_request(groups_url):
53
+ await self.handle_namespace(group, event)
54
+
55
+ async def gitlab_json_request(self, url):
56
+ """Helper that performs an HTTP request and safely returns JSON list."""
57
+ response = await self.api_request(url)
58
+ if response is not None:
59
+ try:
60
+ json_data = response.json()
61
+ except Exception:
62
+ return []
63
+ if json_data and isinstance(json_data, list):
64
+ return json_data
65
+ return []
66
+
67
+ async def handle_namespace(self, namespace, event):
68
+ namespace_name = namespace.get("path", "")
69
+ namespace_url = namespace.get("web_url", "")
70
+ namespace_path = namespace.get("full_path", "")
71
+
72
+ if not (namespace_name and namespace_url and namespace_path):
73
+ return
74
+
75
+ namespace_url = self.helpers.parse_url(namespace_url)._replace(path=f"/{namespace_path}").geturl()
76
+
77
+ social_event = self.make_event(
78
+ {
79
+ "platform": "gitlab",
80
+ "profile_name": namespace_path,
81
+ "url": namespace_url,
82
+ },
83
+ "SOCIAL",
84
+ parent=event,
85
+ )
86
+ await self.emit_event(
87
+ social_event,
88
+ context=f'{{module}} found GitLab namespace ({{event.type}}) "{namespace_name}" at {namespace_url}',
89
+ )
90
+
91
+ # ------------------------------------------------------------------
92
+ # Utility helpers
93
+ # ------------------------------------------------------------------
94
+ def get_base_url(self, event):
95
+ base_url = event.data.get("url", "")
96
+ if not base_url:
97
+ base_url = f"https://{event.host}"
98
+ return self.helpers.urlparse(base_url)._replace(path="/").geturl()
@@ -41,11 +41,14 @@ class trufflehog(BaseModule):
41
41
 
42
42
  scope_distance_modifier = 2
43
43
 
44
- async def setup(self):
45
- self.verified = self.config.get("only_verified", True)
44
+ async def setup_deps(self):
46
45
  self.config_file = self.config.get("config", "")
47
46
  if self.config_file:
48
47
  self.config_file = await self.helpers.wordlist(self.config_file)
48
+ return True
49
+
50
+ async def setup(self):
51
+ self.verified = self.config.get("only_verified", True)
49
52
  self.concurrency = int(self.config.get("concurrency", 8))
50
53
 
51
54
  self.deleted_forks = self.config.get("deleted_forks", False)
bbot/scanner/scanner.py CHANGED
@@ -484,7 +484,7 @@ class Scanner:
484
484
  for module in self.modules.values():
485
485
  module.start()
486
486
 
487
- async def setup_modules(self, remove_failed=True):
487
+ async def setup_modules(self, remove_failed=True, deps_only=False):
488
488
  """Asynchronously initializes all loaded modules by invoking their `setup()` methods.
489
489
 
490
490
  Args:
@@ -509,7 +509,7 @@ class Scanner:
509
509
  hard_failed = []
510
510
  soft_failed = []
511
511
 
512
- async for task in self.helpers.as_completed([m._setup() for m in self.modules.values()]):
512
+ async for task in self.helpers.as_completed([m._setup(deps_only=deps_only) for m in self.modules.values()]):
513
513
  module, status, msg = await task
514
514
  if status is True:
515
515
  self.debug(f"Setup succeeded for {module.name} ({msg})")
@@ -342,6 +342,31 @@ async def test_modules_basic_perdomainonly(bbot_scanner, monkeypatch):
342
342
  await per_domain_scan._cleanup()
343
343
 
344
344
 
345
+ @pytest.mark.asyncio
346
+ async def test_modules_basic_setup_deps(bbot_scanner):
347
+ from bbot.modules.base import BaseModule
348
+
349
+ class dummy(BaseModule):
350
+ _name = "dummy"
351
+ deps_ran = False
352
+ setup_ran = False
353
+
354
+ async def setup_deps(self):
355
+ self.deps_ran = True
356
+ return True
357
+
358
+ async def setup(self):
359
+ self.setup_ran = True
360
+ return True
361
+
362
+ scan = bbot_scanner()
363
+ scan.modules["dummy"] = dummy(scan)
364
+ await scan.setup_modules(deps_only=True)
365
+ assert scan.modules["dummy"].deps_ran
366
+ assert not scan.modules["dummy"].setup_ran
367
+ await scan._cleanup()
368
+
369
+
345
370
  @pytest.mark.asyncio
346
371
  async def test_modules_basic_stats(helpers, events, bbot_scanner, httpx_mock, monkeypatch):
347
372
  from bbot.modules.base import BaseModule
@@ -0,0 +1,66 @@
1
+ from .base import ModuleTestBase
2
+
3
+
4
+ class TestGitlab_Com(ModuleTestBase):
5
+ targets = ["http://127.0.0.1:8888"]
6
+ modules_overrides = ["gitlab_com", "httpx", "social", "excavate"]
7
+
8
+ async def setup_before_prep(self, module_test):
9
+ module_test.httpserver.expect_request("/").respond_with_data("<a href='https://gitlab.org/veilidgroup'/>")
10
+ module_test.httpx_mock.add_response(
11
+ url="https://gitlab.org/api/v4/groups/veilidgroup/projects?simple=true",
12
+ json=[
13
+ {
14
+ "id": 55490429,
15
+ "description": None,
16
+ "name": "Veilid",
17
+ "name_with_namespace": "Veilid / Veilid",
18
+ "path": "veilid",
19
+ "path_with_namespace": "veilidgroup/veilid",
20
+ "created_at": "2024-03-03T05:22:53.169Z",
21
+ "default_branch": "master",
22
+ "tag_list": [],
23
+ "topics": [],
24
+ "ssh_url_to_repo": "git@gitlab.org:veilid/veilid.git",
25
+ "http_url_to_repo": "https://gitlab.org/veilidgroup/veilid.git",
26
+ "web_url": "https://gitlab.org/veilidgroup/veilid",
27
+ "readme_url": "https://gitlab.org/veilidgroup/veilid/-/blob/master/README.md",
28
+ "forks_count": 0,
29
+ "avatar_url": None,
30
+ "star_count": 0,
31
+ "last_activity_at": "2024-03-03T05:22:53.097Z",
32
+ "namespace": {
33
+ "id": 66882294,
34
+ "name": "veilidgroup",
35
+ "path": "veilidgroup",
36
+ "kind": "group",
37
+ "full_path": "veilidgroup",
38
+ "parent_id": None,
39
+ "avatar_url": "/uploads/-/system/group/avatar/66882294/signal-2023-07-04-192426_003.jpeg",
40
+ "web_url": "https://gitlab.org/groups/veilidgroup",
41
+ },
42
+ },
43
+ ],
44
+ )
45
+
46
+ def check(self, module_test, events):
47
+ assert 1 == len(
48
+ [
49
+ e
50
+ for e in events
51
+ if e.type == "SOCIAL"
52
+ and e.data["platform"] == "gitlab"
53
+ and e.data["profile_name"] == "veilidgroup"
54
+ and e.data["url"] == "https://gitlab.org/veilidgroup"
55
+ ]
56
+ )
57
+ assert 1 == len(
58
+ [
59
+ e
60
+ for e in events
61
+ if e.type == "CODE_REPOSITORY"
62
+ and "git" in e.tags
63
+ and e.data["url"] == "https://gitlab.org/veilidgroup/veilid"
64
+ and str(e.module) == "gitlab_com"
65
+ ]
66
+ )
@@ -1,10 +1,10 @@
1
1
  from .base import ModuleTestBase
2
2
 
3
3
 
4
- class TestGitlab(ModuleTestBase):
4
+ class TestGitlab_OnPrem(ModuleTestBase):
5
5
  targets = ["http://127.0.0.1:8888"]
6
- modules_overrides = ["gitlab", "httpx"]
7
- config_overrides = {"modules": {"gitlab": {"api_key": "asdf"}}}
6
+ modules_overrides = ["gitlab_onprem", "httpx"]
7
+ config_overrides = {"modules": {"gitlab_onprem": {"api_key": "asdf"}}}
8
8
 
9
9
  async def setup_before_prep(self, module_test):
10
10
  module_test.httpserver.expect_request("/").respond_with_data(headers={"X-Gitlab-Meta": "asdf"})
@@ -179,7 +179,7 @@ class TestGitlab(ModuleTestBase):
179
179
  and e.data["platform"] == "gitlab"
180
180
  and e.data["profile_name"] == "bbotgroup"
181
181
  and e.data["url"] == "http://127.0.0.1:8888/bbotgroup"
182
- and str(e.module) == "gitlab"
182
+ and str(e.module) == "gitlab_onprem"
183
183
  ]
184
184
  )
185
185
  assert 1 == len(
@@ -209,68 +209,3 @@ class TestGitlab(ModuleTestBase):
209
209
  and e.data["url"] == "http://127.0.0.1:8888/bbotgroup/bbot3"
210
210
  ]
211
211
  )
212
-
213
-
214
- class TestGitlabDotOrg(ModuleTestBase):
215
- targets = ["http://127.0.0.1:8888"]
216
- modules_overrides = ["gitlab", "httpx", "social", "excavate"]
217
-
218
- async def setup_before_prep(self, module_test):
219
- module_test.httpserver.expect_request("/").respond_with_data("<a href='https://gitlab.org/veilidgroup'/>")
220
- module_test.httpx_mock.add_response(
221
- url="https://gitlab.org/api/v4/groups/veilidgroup/projects?simple=true",
222
- json=[
223
- {
224
- "id": 55490429,
225
- "description": None,
226
- "name": "Veilid",
227
- "name_with_namespace": "Veilid / Veilid",
228
- "path": "veilid",
229
- "path_with_namespace": "veilidgroup/veilid",
230
- "created_at": "2024-03-03T05:22:53.169Z",
231
- "default_branch": "master",
232
- "tag_list": [],
233
- "topics": [],
234
- "ssh_url_to_repo": "git@gitlab.org:veilid/veilid.git",
235
- "http_url_to_repo": "https://gitlab.org/veilidgroup/veilid.git",
236
- "web_url": "https://gitlab.org/veilidgroup/veilid",
237
- "readme_url": "https://gitlab.org/veilidgroup/veilid/-/blob/master/README.md",
238
- "forks_count": 0,
239
- "avatar_url": None,
240
- "star_count": 0,
241
- "last_activity_at": "2024-03-03T05:22:53.097Z",
242
- "namespace": {
243
- "id": 66882294,
244
- "name": "veilidgroup",
245
- "path": "veilidgroup",
246
- "kind": "group",
247
- "full_path": "veilidgroup",
248
- "parent_id": None,
249
- "avatar_url": "/uploads/-/system/group/avatar/66882294/signal-2023-07-04-192426_003.jpeg",
250
- "web_url": "https://gitlab.org/groups/veilidgroup",
251
- },
252
- },
253
- ],
254
- )
255
-
256
- def check(self, module_test, events):
257
- assert 1 == len(
258
- [
259
- e
260
- for e in events
261
- if e.type == "SOCIAL"
262
- and e.data["platform"] == "gitlab"
263
- and e.data["profile_name"] == "veilidgroup"
264
- and e.data["url"] == "https://gitlab.org/veilidgroup"
265
- ]
266
- )
267
- assert 1 == len(
268
- [
269
- e
270
- for e in events
271
- if e.type == "CODE_REPOSITORY"
272
- and "git" in e.tags
273
- and e.data["url"] == "https://gitlab.org/veilidgroup/veilid"
274
- and str(e.module) == "gitlab"
275
- ]
276
- )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bbot
3
- Version: 2.7.1.7212rc0
3
+ Version: 2.7.2.7271rc0
4
4
  Summary: OSINT automation for hackers.
5
5
  License: GPL-3.0
6
6
  License-File: LICENSE
@@ -1,5 +1,5 @@
1
- bbot/__init__.py,sha256=VSc_ku85NcucmahEOo7gG1JKRDfMFITM_ihdRv4OvPs,163
2
- bbot/cli.py,sha256=1QJbANVw9Q3GFM92H2QRV2ds5756ulm08CDZwzwPpeI,11888
1
+ bbot/__init__.py,sha256=mzX1fioFjIfbuMCUdqxHKWZtvwkr1KdOqpKsmuve0MA,163
2
+ bbot/cli.py,sha256=lcBQ7TxDxJEQVu89UJSkti_W-rc40Nfw4AlgRUvrT6E,12783
3
3
  bbot/core/__init__.py,sha256=l255GJE_DvUnWvrRb0J5lG-iMztJ8zVvoweDOfegGtI,46
4
4
  bbot/core/config/__init__.py,sha256=zYNw2Me6tsEr8hOOkLb4BQ97GB7Kis2k--G81S8vofU,342
5
5
  bbot/core/config/files.py,sha256=zANvrTRLJQIOWSNkxd9MpWmf9cQFr0gRZLUxeIbTwQc,1412
@@ -9,7 +9,7 @@ bbot/core/engine.py,sha256=DxlbxUWU1x20DTIsVsYXWuR5Z8eYJRmP-SOLyvO4Eek,29362
9
9
  bbot/core/event/__init__.py,sha256=pRi5lC9YBKGxx6ZgrnE4shqKYYdqKR1Ps6tDw2WKGOw,113
10
10
  bbot/core/event/base.py,sha256=1jUgd3I3TDITKoobh92ir_tIm38EN1ZbhoaX1W9fKts,67125
11
11
  bbot/core/event/helpers.py,sha256=MohOCVBjkn_K1p4Ipgx-MKliZtV6l4NJPq3YgagkvSM,6507
12
- bbot/core/flags.py,sha256=Ltvm8Bc4D65I55HuU5bzyjO1R3yMDNpVmreGU83ZBXE,1266
12
+ bbot/core/flags.py,sha256=3bT3BYActmxomZLusjPROzrLhVlRJKLOCluZBPVauEg,1336
13
13
  bbot/core/helpers/__init__.py,sha256=cpOGLKIgA3vdHYqsOtx63BFO_qbtwCmez2amFPu6YTs,111
14
14
  bbot/core/helpers/async_helpers.py,sha256=bVHEUIOZo8iCmuovLYb3oNLPdLFUoEyc6wZIIvtELVs,4399
15
15
  bbot/core/helpers/bloom.py,sha256=fTMdgnrJPC9fzNXwBg1GYbz1P05YPhjxy9aUg6-z3a4,2605
@@ -44,10 +44,10 @@ bbot/core/helpers/web/client.py,sha256=RPm4kYdHzqPom0EdVFvAiUPuDpztW8cuqGaLfl2Tx
44
44
  bbot/core/helpers/web/engine.py,sha256=XmyDMXsJWasOklfPWOcJ6SzuLaUXP3HGJUBO5Gj2xFk,9221
45
45
  bbot/core/helpers/web/envelopes.py,sha256=mMmr4QGi28KJSwCkaogMGYhiqeqm3_kO9KziLtEKefc,10074
46
46
  bbot/core/helpers/web/ssl_context.py,sha256=aWVgl-d0HoE8B4EBKNxaa5UAzQmx79DjDByfBw9tezo,356
47
- bbot/core/helpers/web/web.py,sha256=q9X6JNufbZzRijzsc6bXkxH0ntly7rDr-uptscSKxRo,24042
47
+ bbot/core/helpers/web/web.py,sha256=Imi29a1lxfcFeFpAwGui3Ckzxki3oTBGJ5np-JdGOhc,24088
48
48
  bbot/core/helpers/wordcloud.py,sha256=QM8Z1N01_hXrRFKQjvRL-IzOOC7ZMKjuSBID3u77Sxg,19809
49
49
  bbot/core/helpers/yara_helper.py,sha256=ypwC_H_ovJp9BpOwqgPkIdZEwqWfvqrRdKlwzBLfm8Q,1592
50
- bbot/core/modules.py,sha256=_h-TFaMw7NmpEO1orJ9kVgeVbATbMxo7zlGoa8p9Cs8,29354
50
+ bbot/core/modules.py,sha256=aQtltmNVDBZuvaSzCpI7wxvzXy0thL6z9oN_HIkTYCo,29279
51
51
  bbot/core/multiprocess.py,sha256=ocQHanskJ09gHwe7RZmwNdZyCOQyeyUoIHCtLbtvXUk,1771
52
52
  bbot/core/shared_deps.py,sha256=NeJmyakKxQQjN-H3rYGwGuHeVxDiVM_KnDfeVEeIbf4,9498
53
53
  bbot/db/sql/models.py,sha256=SrUdDOBCICzXJBY29p0VvILhMQ1JCuh725bqvIYogX0,4884
@@ -57,7 +57,7 @@ bbot/logger.py,sha256=wE-532v5FyKuSSoTdyW1xSfaOnLZB1axAJnB-uW2xrI,2745
57
57
  bbot/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
58
58
  bbot/modules/ajaxpro.py,sha256=daE1yQoCsSI5c4dh3YKwRSggTISNjWgrK7qTPidk7cU,3764
59
59
  bbot/modules/anubisdb.py,sha256=JCy2YCfa0e_VawpzNmcPXAosKUthmYGutireJ0gMDws,1916
60
- bbot/modules/apkpure.py,sha256=h26zh-nv1G9IvvTwywbFcARiZPfHUhjIUGVl0Achbek,2555
60
+ bbot/modules/apkpure.py,sha256=q15cOGsj7AfRnOZQAahJefnD1YGzsYWqkFz0RXs3RPk,2567
61
61
  bbot/modules/aspnet_bin_exposure.py,sha256=X16mK8tff7-LOcbS4fUschkDRdVVz9r2N8Ta2EZzw2Y,3737
62
62
  bbot/modules/azure_realm.py,sha256=pP2PUlLy0K9KKaE8aNcznWjDW3PKHvnMejdOSc-o4ms,1612
63
63
  bbot/modules/azure_tenant.py,sha256=qBn7CUA_hth2PqW55XZVjYxIw20xLYrMntXc6mYpmKU,5366
@@ -65,7 +65,7 @@ bbot/modules/baddns.py,sha256=vSWWBiPfVowAg1yrBAx0rm8ViSh1O3VX7nI9Pn2Z5mo,6694
65
65
  bbot/modules/baddns_direct.py,sha256=G5WTKQ-jKJnSLijsOjyQKoR2Sx49fvACBvAC-b_yPck,3820
66
66
  bbot/modules/baddns_zone.py,sha256=8s2vIbU2MsLW6w12bDyw8FWBfdDRn2A2gWoMFEX4gPw,1036
67
67
  bbot/modules/badsecrets.py,sha256=2M515_nmDg24WTDqM01w-2DNN2H8BewvqnG_8hG6n3o,5110
68
- bbot/modules/base.py,sha256=uatsSs15fcaawbu7JTfQiY8WhkKQ-6WGGG41hIT2YeQ,78811
68
+ bbot/modules/base.py,sha256=mRGn1Td9V58P9saNQ8KOJkEGA3fmfwEf6FP_ZuWSIug,78508
69
69
  bbot/modules/bevigil.py,sha256=0VLIxmeXRUI2-EoR6IzuHJMcX8KCHNNta-WYa3gVlDg,2862
70
70
  bbot/modules/bucket_amazon.py,sha256=mwjYeEAcdfOpjbOa1sD8U9KBMMVY_c8FoHjSGR9GQbg,730
71
71
  bbot/modules/bucket_azure.py,sha256=Jaa9XEL7w7VM0a-WAp05MOGdP5nt7hMpLzBsPq74_IM,1284
@@ -86,31 +86,32 @@ bbot/modules/crt_db.py,sha256=xaIm2457_xGJjnKss73l1HpPn7pLPHksVzejsimTfZA,2198
86
86
  bbot/modules/dehashed.py,sha256=0lzcqMEgwRmprwurZ2-8Y8aOO4KTueJgpY_vh0DWQwA,5155
87
87
  bbot/modules/digitorus.py,sha256=XQY0eAQrA7yo8S57tGncP1ARud-yG4LiWxx5VBYID34,1027
88
88
  bbot/modules/dnsbimi.py,sha256=XqqZj_7dipYn-ByXC6IBTJ2bUSY3QOAG4e8F40kHuAI,6931
89
- bbot/modules/dnsbrute.py,sha256=Y2bSbG2IcwIJID1FSQ6Qe9fdpWwG7GIO-wVQw7MdQFM,2439
89
+ bbot/modules/dnsbrute.py,sha256=w396XpfNmU3QDnByrgYL4FNXuD9sV9TA_jnYGw41nT8,2607
90
90
  bbot/modules/dnsbrute_mutations.py,sha256=EbAZ-ZOqk98OAMacc8PuX_zx6eXyn6gJxgFuZ8A71YA,7242
91
91
  bbot/modules/dnscaa.py,sha256=pyaLqHrdsVhqtd1JBZVjKKcuYT_ywUbFYkrnfXcGD5s,5014
92
92
  bbot/modules/dnscommonsrv.py,sha256=wrCRTlqVuxFIScWH0Cb0UQAVk0TWxgVc5fo5awl3R24,1568
93
93
  bbot/modules/dnsdumpster.py,sha256=x4_1ZcPRAKDiCWMt7x4bbfcar2-VN6fLjWx0ijPUEmY,2775
94
94
  bbot/modules/dnstlsrpt.py,sha256=v8V72RBsawmDPrMrTcKXEyoFt9bgbfm-cpoPYgKEKLQ,6238
95
- bbot/modules/docker_pull.py,sha256=zNQcQdS-JWM2-TbQ_iyjeGA9CKcpuXdeO5ucoJgzZNY,9189
95
+ bbot/modules/docker_pull.py,sha256=G1B4mjZBFIfxlbmi_Q02-Y9N53moRKpjQ7Li_aU_MYM,9201
96
96
  bbot/modules/dockerhub.py,sha256=JQkujjqvQRzQuvHjQ7JbFs_VlJj8dLRPRObAkBgUQhc,3493
97
97
  bbot/modules/dotnetnuke.py,sha256=zipcHyNYr2FEecStb1Yrm938ps01RvHV8NnyqAvnGGc,10537
98
98
  bbot/modules/emailformat.py,sha256=Koi2aSng-FSRJVhpbFaclrqZxo4lQoPMcUMn_qXTfVE,1518
99
99
  bbot/modules/extractous.py,sha256=VSGKmHPAA_4r62jaN8Yqi3JcjehjxpI2lhe8i2j786s,4648
100
- bbot/modules/ffuf.py,sha256=94TJ5xvqKwH0JaWmC_t1dLTpRsO8HEy4lnbsu8LF_HY,14965
101
- bbot/modules/ffuf_shortnames.py,sha256=y5vnypLPN-KrjpmoG5zlqcX8VwfcLBpNg1yQI7bP9Hg,18737
102
- bbot/modules/filedownload.py,sha256=5MctNWSYyjoXPshRXbltsn92KDAr9fsLqbPGP4eK7Es,8903
100
+ bbot/modules/ffuf.py,sha256=M08iRvt7HjpmwuvmS8OgKhCuNg2PU1LbZt_DZmZauEs,15033
101
+ bbot/modules/ffuf_shortnames.py,sha256=saAW_csgMyGd1OVAPQa5hfdHkxf98VWRPiUIZWm29LY,18790
102
+ bbot/modules/filedownload.py,sha256=IOmoK7_Y784hkUP27ImMFWcHqOYg441KJ0XkoLV6KFg,8968
103
103
  bbot/modules/fingerprintx.py,sha256=rdlR9d64AntAhbS_eJzh8bZCeLPTJPSKdkdKdhH_qAo,3269
104
104
  bbot/modules/fullhunt.py,sha256=2ntu1yBh51N4e_l-kpXc1UBoVVcxEE2JPkyaMYCuUb4,1336
105
105
  bbot/modules/generic_ssrf.py,sha256=KFdcHpUV9-Z7oN7emzbirimsNc2xZ_1IFqnsfIkEbcM,9196
106
106
  bbot/modules/git.py,sha256=zmHeI0bn181T1P8C55HSebkdVGLTpzGxPc-LRqiHrbc,1723
107
- bbot/modules/git_clone.py,sha256=SwtCnOpVqEgSMfqaN54NUpS2jYZWt4Fk8Y_TqUIO724,3764
108
- bbot/modules/gitdumper.py,sha256=mzlEJuWLlZIWXj-0V5kC8qTVLEvVtbrPColCXQGFEoQ,11588
107
+ bbot/modules/git_clone.py,sha256=DzYQ5p_ZmezxSXKamjcxTVB0RNIywehs_UvtnFFVW2g,3776
108
+ bbot/modules/gitdumper.py,sha256=F1qpErD3dv4qvWODgLi8MWMENfJxrl12OAVSbusiF34,11600
109
109
  bbot/modules/github_codesearch.py,sha256=a-r2vE9N9WyBpFUiKCsg0TK4Qn7DaEGyVRTUKzkDLWA,3641
110
110
  bbot/modules/github_org.py,sha256=WM18vJCHuOHJJ5rPzQzQ3Pmp7XPPuaMeVgNfW-FlO0k,8938
111
111
  bbot/modules/github_usersearch.py,sha256=G8knkQBJsn7EKcMhcEaFPiB_Y5S96e2VaseBubsqOyk,3407
112
- bbot/modules/github_workflows.py,sha256=xKntAFDeGuE4MqbEmhJyYXKbzoSh9tWYlHNlnF37PYA,10040
113
- bbot/modules/gitlab.py,sha256=9oWWpBijeHCjuFBfWW4HvNqt7bvJvrBgBjaaz_UPPnE,5964
112
+ bbot/modules/github_workflows.py,sha256=cj9YLoW01v5Iey6s89zShlrAF80TEBhFcEWyrvdNe38,10052
113
+ bbot/modules/gitlab_com.py,sha256=WBNGw4ec-xd_Iz8yxJcxEgTOpsBPxfn5pDU1DtONFgs,1051
114
+ bbot/modules/gitlab_onprem.py,sha256=OwbYeldAUCQvFiYAIikX1-waHii1F0cMPLAtqc4pyHs,3622
114
115
  bbot/modules/google_playstore.py,sha256=N4QjzQag_bgDXfX17rytBiiWA-SQtYI2N0J_ZNEOdv0,3701
115
116
  bbot/modules/gowitness.py,sha256=hMhCz4O1sDJCzCzRIcmu0uNDgDDf9JzkFBwL1WuUum0,13144
116
117
  bbot/modules/graphql_introspection.py,sha256=Y-MqXrN6qmXTv2T6t7hJ-SU3R2guZQRWkrrCLC56bAc,4239
@@ -143,7 +144,7 @@ bbot/modules/lightfuzz/submodules/serial.py,sha256=Vry3J0Bs3QJqgVPzxhDFmEZQt4FYz
143
144
  bbot/modules/lightfuzz/submodules/sqli.py,sha256=HX0wP-aVn02zzBDujpLgzXPos7w_eiSiALTNCN2O_Bo,8597
144
145
  bbot/modules/lightfuzz/submodules/ssti.py,sha256=Pib49rXFuf567msnlec-A1Tnvolw4aILjqn7INLWQTY,1413
145
146
  bbot/modules/lightfuzz/submodules/xss.py,sha256=BZz1_nqzV8dqJptpoqZEMdVBdtZHmRae3HWo3S9yzIc,9507
146
- bbot/modules/medusa.py,sha256=44psluRg9VFo6WNLKlnTtH66afqhrOF0STBbLhFkHCE,8859
147
+ bbot/modules/medusa.py,sha256=K_U_s2cZJi8UvzMB8qN61qj_x0uD16xdZUpC5ejUaGA,8729
147
148
  bbot/modules/myssl.py,sha256=DoMF7o6MxIrcglCrC-W3nM-GPcyJRM4PlGdKfnOlIvs,942
148
149
  bbot/modules/newsletters.py,sha256=1Q4JjShPsxHJ-by2CbGfCvEt80blUGPX0hxQIzB_a9M,2630
149
150
  bbot/modules/ntlm.py,sha256=EGmb4k3YC_ZuHIU3mGUZ4yaMjE35wVQQSv8HwTsQJzY,4391
@@ -175,13 +176,13 @@ bbot/modules/output/web_report.py,sha256=lZ0FqRZ7Jz1lljI9JMhH9gjtWLaTCSpSnAKQGAc
175
176
  bbot/modules/output/websocket.py,sha256=oxMcYu3hIrA3BE3c3aJXe63JmGv_HWjADB2uO470-BE,2721
176
177
  bbot/modules/paramminer_cookies.py,sha256=q1PzftHQpCHLz81_VgLZsO6moia7ZtnU32igfcySi2w,1816
177
178
  bbot/modules/paramminer_getparams.py,sha256=_j6rgaqV5wGJoa8p5-KKbe2YsVGUtmWIanCVtFiF97Y,1893
178
- bbot/modules/paramminer_headers.py,sha256=GMErLmTO0w7JRIpJE2VFvRTrjmoux_-jTx3EfaWLdpM,10518
179
+ bbot/modules/paramminer_headers.py,sha256=W9fpSwFmrhiWt6wIrhzeksSbzDFzjhg6ncyEzucCm04,10596
179
180
  bbot/modules/passivetotal.py,sha256=dHYk9QWIKdO6Z8Bip4IdcButiqy_fr4FrpRUQaiH1a0,1678
180
181
  bbot/modules/pgp.py,sha256=Xu2M9WEIlwTm5-Lv29g7BblI05tD9Dl0XsYSeY6UURs,2065
181
182
  bbot/modules/portfilter.py,sha256=3iu4xqCsHafhVMbA32Mw6K_7Yn576Rz6GxXMevZQEpM,1752
182
183
  bbot/modules/portscan.py,sha256=emhNhnFYBVMnVm7IZKmzHJRCKRwVpF3S9dtOV6H9iYA,13091
183
184
  bbot/modules/postman.py,sha256=vo761Nzu3kPBzfCY3KJcvsGEsjImaa7iA2z-LyASBDc,4589
184
- bbot/modules/postman_download.py,sha256=OXC4hHInwD2Ps-BURc2uM_Z7PCl95KPdcxSI34Vzdcc,3604
185
+ bbot/modules/postman_download.py,sha256=nR7Zpy8DsF1xwPskWysr4i9Bhn0JQq9E__A-UcjjDM8,3616
185
186
  bbot/modules/rapiddns.py,sha256=uONESr0B5pv9cSAr7lF4WWV31APUhXyHexvI04rUcyk,787
186
187
  bbot/modules/reflected_parameters.py,sha256=RjS-4C-XC9U-jC9J7AYNqwn6I-O2y3LvTRhB68dpgKI,3281
187
188
  bbot/modules/report/affiliates.py,sha256=vvus8LylqOfP-lfGid0z4FS6MwOpNuRTcSJ9aSnybp4,1713
@@ -203,13 +204,14 @@ bbot/modules/subdomainradar.py,sha256=YlRNMtNGLpa13KZ7aksAMVZdSjxe1tkywU5RXlwXpP
203
204
  bbot/modules/telerik.py,sha256=kWi498zihl02gHaS7AvyAxlEAZvmfKgKMSTAG8CS62A,19108
204
205
  bbot/modules/templates/bucket.py,sha256=muLPpfAGtcNhL0tLU-qHTlTNIz4yncRcVjdZMqVRtUI,7153
205
206
  bbot/modules/templates/github.py,sha256=lrV1EYPqjtPkJsS0fQfqmLvGchNo_fO3A75W9-03gxY,2531
207
+ bbot/modules/templates/gitlab.py,sha256=XOwCaYO77ISbVPnjzws2M1klueTnJbXRef-ZsHUtwvA,3895
206
208
  bbot/modules/templates/postman.py,sha256=MIpz2q_r6LP0kIEgByo7oX5qHhMZLOhr7oKzJI9Beec,6959
207
209
  bbot/modules/templates/shodan.py,sha256=MXBvlmfw3jZFqT47v10UkqMSyQR-zBIxMJmK7PWw6uw,1174
208
210
  bbot/modules/templates/sql.py,sha256=o-CdyyoJvHJdJBKkj3CIGXYxUta4w2AB_2Vr-k7cDDU,3553
209
211
  bbot/modules/templates/subdomain_enum.py,sha256=epyKSly08jqaINV_AMMWbNafIeQjJqvd3aj63KD0Mck,8402
210
212
  bbot/modules/templates/webhook.py,sha256=uGFmcJ81GzGN1UI2k2O7nQF_fyh4ehLDEg2NSXaPnhk,3373
211
213
  bbot/modules/trickest.py,sha256=MRgLW0YiDWzlWdAjyqfPPLFb-a51r-Ffn_dphiJI_gA,1550
212
- bbot/modules/trufflehog.py,sha256=uW8smAJUwU5PM6jnIE_wlsz-EwgaJD-iaHmFciX59H8,8717
214
+ bbot/modules/trufflehog.py,sha256=aMHaruUX6ZdPZ-SI8iIHBAsfEqIXz3GqZtynaFeEWnc,8770
213
215
  bbot/modules/url_manipulation.py,sha256=4J3oFkqTSJPPmbKEKAHJg2Q2w4QNKtQhiN03ZJq5VtI,4326
214
216
  bbot/modules/urlscan.py,sha256=-w_3Bm6smyG2GLQyIbnMUkKmeQVauo-V6F4_kJDYG7s,3740
215
217
  bbot/modules/vhost.py,sha256=cirOe0HR4M0TEBN8JdXo2l0s2flc8ZSdxggGm79blT8,5459
@@ -255,7 +257,7 @@ bbot/scanner/preset/conditions.py,sha256=hFL9cSIWGEsv2TfM5UGurf0c91cyaM8egb5IngB
255
257
  bbot/scanner/preset/environ.py,sha256=9KbEOLWkUdoAf5Ez_2A1NNm6QduQElbnNnrPi6VDhZs,4731
256
258
  bbot/scanner/preset/path.py,sha256=X32-ZUmL7taIv37VKF1KfmeiK9fjuQOE7pWUTEbPK8c,2483
257
259
  bbot/scanner/preset/preset.py,sha256=G_aMMI33d2OlzNUwjfi5ddJdxa8nK0oF5HrYAsuregU,40708
258
- bbot/scanner/scanner.py,sha256=Zz_syqeBpzc0Df1RH5HizylM2IPbFe7ZKd6e9quHWY4,55578
260
+ bbot/scanner/scanner.py,sha256=RoVq12XXGNL4I7zu9uqBk0UG70xUpjuaYsrbALEHlho,55614
259
261
  bbot/scanner/stats.py,sha256=re93sArKXZSiD0Owgqk2J3Kdvfm3RL4Y9Qy_VOcaVk8,3623
260
262
  bbot/scanner/target.py,sha256=lI0Tn5prQiPiJE3WW-ZLx_l6EFqzAVabtyL-nfXJ8cE,10636
261
263
  bbot/scripts/benchmark_report.py,sha256=kBMaKFrmURk8tFYbGKvkGQpfNxrX46CBWG9DdaXhHgs,16230
@@ -293,7 +295,7 @@ bbot/test/test_step_1/test_files.py,sha256=5Q_3jPpMXULxDHsanSDUaj8zF8bXzKdiJZHOm
293
295
  bbot/test/test_step_1/test_helpers.py,sha256=7GP6-95yWRBGhx0-p6N7zZVEDcF9EO9wc0rkhs1JDsg,40281
294
296
  bbot/test/test_step_1/test_manager_deduplication.py,sha256=hZQpDXzg6zvzxFolVOcJuY-ME8NXjZUsqS70BRNXp8A,15594
295
297
  bbot/test/test_step_1/test_manager_scope_accuracy.py,sha256=JV1bQHt9EIM0GmGS4T4Brz_L2lfcwTxtNC06cfv7r64,79763
296
- bbot/test/test_step_1/test_modules_basic.py,sha256=ELpGlsthSq8HaxB5My8-ESVHqMxqdL5Of0STMIyaWzA,20001
298
+ bbot/test/test_step_1/test_modules_basic.py,sha256=1A5saKsZ254cgdv5o9pk7iU5g6kxmcCeLfuLiYIsihs,20640
297
299
  bbot/test/test_step_1/test_presets.py,sha256=HnJhKwDnVh9Y6adgxqe85677rWpnFil_WS5GjX21ZvM,40959
298
300
  bbot/test/test_step_1/test_python_api.py,sha256=Fk5bxEsPSjsMZ_CcRMTJft8I48EizwHJivG9Fy4jIu0,5502
299
301
  bbot/test/test_step_1/test_regexes.py,sha256=GEJE4NY6ge0WnG3BcFgRiT78ksy2xpFk6UdS9vGQMPs,15254
@@ -369,7 +371,8 @@ bbot/test/test_step_2/module_tests/test_module_github_codesearch.py,sha256=M50xB
369
371
  bbot/test/test_step_2/module_tests/test_module_github_org.py,sha256=5tKO6NH4TPBeIdeTf7Bz9PUZ1pcvKsjrG0nFhc3YgT0,25458
370
372
  bbot/test/test_step_2/module_tests/test_module_github_usersearch.py,sha256=IIQ0tYZjQN8_L8u_N4m8Nz3kbB4IyBp95tYCPcQeScg,5264
371
373
  bbot/test/test_step_2/module_tests/test_module_github_workflows.py,sha256=o_teEaskm3H22QEKod5KJayFvvcgOQoG4eItGWv8C8E,38006
372
- bbot/test/test_step_2/module_tests/test_module_gitlab.py,sha256=fnwE7BWTU6EQquKdGLCiaX_LwVwvzOLev3Y9GheTLSY,11859
374
+ bbot/test/test_step_2/module_tests/test_module_gitlab_com.py,sha256=fGnjYyMvMZE2hu0Fms9H8rMnPPN6_uynDDDEmcVE9-8,2753
375
+ bbot/test/test_step_2/module_tests/test_module_gitlab_onprem.py,sha256=Soo72Ppt5hYWVUIxMYGnBGPL47EnVDPbTsEHUziKimg,9173
373
376
  bbot/test/test_step_2/module_tests/test_module_google_playstore.py,sha256=uTRqpAGI9HI-rOk_6jdV44OoSqi0QQQ3aTVzvuV0dtc,3034
374
377
  bbot/test/test_step_2/module_tests/test_module_gowitness.py,sha256=8kSeBowX4eejMW791mIaFqP9SDn1l2EDRJatvmZVWug,6500
375
378
  bbot/test/test_step_2/module_tests/test_module_graphql_introspection.py,sha256=qac8DJ_exe6Ra4UgRvVMSdgBhLIZP9lmXyKhi9RPOK8,1241
@@ -460,8 +463,8 @@ bbot/wordlists/raft-small-extensions-lowercase_CLEANED.txt,sha256=ZSIVebs7ptMvHx
460
463
  bbot/wordlists/top_open_ports_nmap.txt,sha256=LmdFYkfapSxn1pVuQC2LkOIY2hMLgG-Xts7DVtYzweM,42727
461
464
  bbot/wordlists/valid_url_schemes.txt,sha256=0B_VAr9Dv7aYhwi6JSBDU-3M76vNtzN0qEC_RNLo7HE,3310
462
465
  bbot/wordlists/wordninja_dns.txt.gz,sha256=DYHvvfW0TvzrVwyprqODAk4tGOxv5ezNmCPSdPuDUnQ,570241
463
- bbot-2.7.1.7212rc0.dist-info/METADATA,sha256=7rDrwHdnOGfp7sZS3vDHbnlFYUnX_d7gwDpya7NkQY4,18420
464
- bbot-2.7.1.7212rc0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
465
- bbot-2.7.1.7212rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
466
- bbot-2.7.1.7212rc0.dist-info/licenses/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
467
- bbot-2.7.1.7212rc0.dist-info/RECORD,,
466
+ bbot-2.7.2.7271rc0.dist-info/METADATA,sha256=_oIi9ERuKbl0buhGO6vJ8BJku9jGX5p0XII95-JjNOg,18420
467
+ bbot-2.7.2.7271rc0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
468
+ bbot-2.7.2.7271rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
469
+ bbot-2.7.2.7271rc0.dist-info/licenses/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
470
+ bbot-2.7.2.7271rc0.dist-info/RECORD,,
bbot/modules/gitlab.py DELETED
@@ -1,141 +0,0 @@
1
- from bbot.modules.base import BaseModule
2
-
3
-
4
- class gitlab(BaseModule):
5
- watched_events = ["HTTP_RESPONSE", "TECHNOLOGY", "SOCIAL"]
6
- produced_events = ["TECHNOLOGY", "SOCIAL", "CODE_REPOSITORY", "FINDING"]
7
- flags = ["active", "safe", "code-enum"]
8
- meta = {
9
- "description": "Detect GitLab instances and query them for repositories",
10
- "created_date": "2024-03-11",
11
- "author": "@TheTechromancer",
12
- }
13
- options = {"api_key": ""}
14
- options_desc = {"api_key": "Gitlab access token"}
15
-
16
- scope_distance_modifier = 2
17
-
18
- async def setup(self):
19
- await self.require_api_key()
20
- return True
21
-
22
- async def filter_event(self, event):
23
- # only accept out-of-scope SOCIAL events
24
- if event.type == "HTTP_RESPONSE":
25
- if event.scope_distance > self.scan.scope_search_distance:
26
- return False, "event is out of scope distance"
27
- elif event.type == "TECHNOLOGY":
28
- if not event.data["technology"].lower().startswith("gitlab"):
29
- return False, "technology is not gitlab"
30
- if not self.helpers.is_ip(event.host) and self.helpers.tldextract(event.host).domain == "gitlab":
31
- return False, "gitlab instance is not self-hosted"
32
- elif event.type == "SOCIAL":
33
- if event.data["platform"] != "gitlab":
34
- return False, "platform is not gitlab"
35
- return True
36
-
37
- async def handle_event(self, event):
38
- if event.type == "HTTP_RESPONSE":
39
- await self.handle_http_response(event)
40
- elif event.type == "TECHNOLOGY":
41
- await self.handle_technology(event)
42
- elif event.type == "SOCIAL":
43
- await self.handle_social(event)
44
-
45
- async def handle_http_response(self, event):
46
- # identify gitlab instances from HTTP responses
47
- # HTTP_RESPONSE --> TECHNOLOGY
48
- # HTTP_RESPONSE --> FINDING
49
- headers = event.data.get("header", {})
50
- if "x_gitlab_meta" in headers:
51
- url = event.parsed_url._replace(path="/").geturl()
52
- await self.emit_event(
53
- {"host": str(event.host), "technology": "GitLab", "url": url},
54
- "TECHNOLOGY",
55
- parent=event,
56
- context=f"{{module}} detected {{event.type}}: GitLab at {url}",
57
- )
58
- description = f"GitLab server at {event.host}"
59
- await self.emit_event(
60
- {"host": str(event.host), "description": description},
61
- "FINDING",
62
- parent=event,
63
- context=f"{{module}} detected {{event.type}}: {description}",
64
- )
65
-
66
- async def handle_technology(self, event):
67
- # retrieve gitlab groups from gitlab instances
68
- # TECHNOLOGY --> SOCIAL
69
- # TECHNOLOGY --> URL
70
- # TECHNOLOGY --> CODE_REPOSITORY
71
- base_url = self.get_base_url(event)
72
- projects_url = self.helpers.urljoin(base_url, "api/v4/projects?simple=true")
73
- await self.handle_projects_url(projects_url, event)
74
- groups_url = self.helpers.urljoin(base_url, "api/v4/groups?simple=true")
75
- await self.handle_groups_url(groups_url, event)
76
-
77
- async def handle_social(self, event):
78
- # retrieve repositories from gitlab user
79
- # SOCIAL --> CODE_REPOSITORY
80
- # SOCIAL --> SOCIAL
81
- username = event.data.get("profile_name", "")
82
- if not username:
83
- return
84
- base_url = self.get_base_url(event)
85
- urls = [
86
- # group
87
- self.helpers.urljoin(base_url, f"api/v4/users/{username}/projects?simple=true"),
88
- # user
89
- self.helpers.urljoin(base_url, f"api/v4/groups/{username}/projects?simple=true"),
90
- ]
91
- for url in urls:
92
- await self.handle_projects_url(url, event)
93
-
94
- async def handle_projects_url(self, projects_url, event):
95
- for project in await self.gitlab_json_request(projects_url):
96
- project_url = project.get("web_url", "")
97
- if project_url:
98
- code_event = self.make_event({"url": project_url}, "CODE_REPOSITORY", tags="git", parent=event)
99
- await self.emit_event(
100
- code_event, context=f"{{module}} enumerated projects and found {{event.type}} at {project_url}"
101
- )
102
- namespace = project.get("namespace", {})
103
- if namespace:
104
- await self.handle_namespace(namespace, event)
105
-
106
- async def handle_groups_url(self, groups_url, event):
107
- for group in await self.gitlab_json_request(groups_url):
108
- await self.handle_namespace(group, event)
109
-
110
- async def gitlab_json_request(self, url):
111
- response = await self.api_request(url)
112
- if response is not None:
113
- try:
114
- json = response.json()
115
- except Exception:
116
- return []
117
- if json and isinstance(json, list):
118
- return json
119
- return []
120
-
121
- async def handle_namespace(self, namespace, event):
122
- namespace_name = namespace.get("path", "")
123
- namespace_url = namespace.get("web_url", "")
124
- namespace_path = namespace.get("full_path", "")
125
- if namespace_name and namespace_url and namespace_path:
126
- namespace_url = self.helpers.parse_url(namespace_url)._replace(path=f"/{namespace_path}").geturl()
127
- social_event = self.make_event(
128
- {"platform": "gitlab", "profile_name": namespace_path, "url": namespace_url},
129
- "SOCIAL",
130
- parent=event,
131
- )
132
- await self.emit_event(
133
- social_event,
134
- context=f'{{module}} found GitLab namespace ({{event.type}}) "{namespace_name}" at {namespace_url}',
135
- )
136
-
137
- def get_base_url(self, event):
138
- base_url = event.data.get("url", "")
139
- if not base_url:
140
- base_url = f"https://{event.host}"
141
- return self.helpers.urlparse(base_url)._replace(path="/").geturl()