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 +1 -1
- bbot/cli.py +21 -7
- bbot/core/flags.py +1 -0
- bbot/core/helpers/web/web.py +2 -1
- bbot/core/modules.py +0 -2
- bbot/modules/apkpure.py +1 -1
- bbot/modules/base.py +23 -28
- bbot/modules/dnsbrute.py +6 -1
- bbot/modules/docker_pull.py +1 -1
- bbot/modules/ffuf.py +4 -1
- bbot/modules/ffuf_shortnames.py +6 -3
- bbot/modules/filedownload.py +7 -4
- bbot/modules/git_clone.py +1 -1
- bbot/modules/gitdumper.py +1 -1
- bbot/modules/github_workflows.py +1 -1
- bbot/modules/gitlab_com.py +31 -0
- bbot/modules/gitlab_onprem.py +84 -0
- bbot/modules/medusa.py +4 -7
- bbot/modules/paramminer_headers.py +10 -7
- bbot/modules/postman_download.py +1 -1
- bbot/modules/templates/gitlab.py +98 -0
- bbot/modules/trufflehog.py +5 -2
- bbot/scanner/scanner.py +2 -2
- bbot/test/test_step_1/test_modules_basic.py +25 -0
- bbot/test/test_step_2/module_tests/test_module_gitlab_com.py +66 -0
- bbot/test/test_step_2/module_tests/{test_module_gitlab.py → test_module_gitlab_onprem.py} +4 -69
- {bbot-2.7.1.7212rc0.dist-info → bbot-2.7.2.7271rc0.dist-info}/METADATA +1 -1
- {bbot-2.7.1.7212rc0.dist-info → bbot-2.7.2.7271rc0.dist-info}/RECORD +31 -28
- bbot/modules/gitlab.py +0 -141
- {bbot-2.7.1.7212rc0.dist-info → bbot-2.7.2.7271rc0.dist-info}/WHEEL +0 -0
- {bbot-2.7.1.7212rc0.dist-info → bbot-2.7.2.7271rc0.dist-info}/entry_points.txt +0 -0
- {bbot-2.7.1.7212rc0.dist-info → bbot-2.7.2.7271rc0.dist-info}/licenses/LICENSE +0 -0
bbot/__init__.py
CHANGED
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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",
|
bbot/core/helpers/web/web.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
651
|
-
if
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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()
|
bbot/modules/docker_pull.py
CHANGED
|
@@ -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)
|
bbot/modules/ffuf_shortnames.py
CHANGED
|
@@ -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
|
|
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")
|
bbot/modules/filedownload.py
CHANGED
|
@@ -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",
|
bbot/modules/github_workflows.py
CHANGED
|
@@ -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
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
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)
|
bbot/modules/postman_download.py
CHANGED
|
@@ -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()
|
bbot/modules/trufflehog.py
CHANGED
|
@@ -41,11 +41,14 @@ class trufflehog(BaseModule):
|
|
|
41
41
|
|
|
42
42
|
scope_distance_modifier = 2
|
|
43
43
|
|
|
44
|
-
async def
|
|
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
|
|
4
|
+
class TestGitlab_OnPrem(ModuleTestBase):
|
|
5
5
|
targets = ["http://127.0.0.1:8888"]
|
|
6
|
-
modules_overrides = ["
|
|
7
|
-
config_overrides = {"modules": {"
|
|
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) == "
|
|
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,5 +1,5 @@
|
|
|
1
|
-
bbot/__init__.py,sha256=
|
|
2
|
-
bbot/cli.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
101
|
-
bbot/modules/ffuf_shortnames.py,sha256=
|
|
102
|
-
bbot/modules/filedownload.py,sha256=
|
|
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=
|
|
108
|
-
bbot/modules/gitdumper.py,sha256=
|
|
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=
|
|
113
|
-
bbot/modules/
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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/
|
|
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.
|
|
464
|
-
bbot-2.7.
|
|
465
|
-
bbot-2.7.
|
|
466
|
-
bbot-2.7.
|
|
467
|
-
bbot-2.7.
|
|
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()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|