bbot 2.7.1.7212rc0__py3-none-any.whl → 2.7.2__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/modules/gitlab_com.py +31 -0
- bbot/modules/gitlab_onprem.py +84 -0
- bbot/modules/templates/gitlab.py +98 -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.dist-info}/METADATA +1 -1
- {bbot-2.7.1.7212rc0.dist-info → bbot-2.7.2.dist-info}/RECORD +11 -8
- bbot/modules/gitlab.py +0 -141
- {bbot-2.7.1.7212rc0.dist-info → bbot-2.7.2.dist-info}/WHEEL +0 -0
- {bbot-2.7.1.7212rc0.dist-info → bbot-2.7.2.dist-info}/entry_points.txt +0 -0
- {bbot-2.7.1.7212rc0.dist-info → bbot-2.7.2.dist-info}/licenses/LICENSE +0 -0
bbot/__init__.py
CHANGED
|
@@ -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)
|
|
@@ -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()
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
bbot/__init__.py,sha256=
|
|
1
|
+
bbot/__init__.py,sha256=S0ODdoaoPWCYtciWgZ3xNYQd6vstpqSDseN8_oArvrM,156
|
|
2
2
|
bbot/cli.py,sha256=1QJbANVw9Q3GFM92H2QRV2ds5756ulm08CDZwzwPpeI,11888
|
|
3
3
|
bbot/core/__init__.py,sha256=l255GJE_DvUnWvrRb0J5lG-iMztJ8zVvoweDOfegGtI,46
|
|
4
4
|
bbot/core/config/__init__.py,sha256=zYNw2Me6tsEr8hOOkLb4BQ97GB7Kis2k--G81S8vofU,342
|
|
@@ -110,7 +110,8 @@ bbot/modules/github_codesearch.py,sha256=a-r2vE9N9WyBpFUiKCsg0TK4Qn7DaEGyVRTUKzk
|
|
|
110
110
|
bbot/modules/github_org.py,sha256=WM18vJCHuOHJJ5rPzQzQ3Pmp7XPPuaMeVgNfW-FlO0k,8938
|
|
111
111
|
bbot/modules/github_usersearch.py,sha256=G8knkQBJsn7EKcMhcEaFPiB_Y5S96e2VaseBubsqOyk,3407
|
|
112
112
|
bbot/modules/github_workflows.py,sha256=xKntAFDeGuE4MqbEmhJyYXKbzoSh9tWYlHNlnF37PYA,10040
|
|
113
|
-
bbot/modules/
|
|
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
|
|
@@ -203,6 +204,7 @@ 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
|
|
@@ -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.dist-info/METADATA,sha256=OBVwJyRnzKqlsC7cCib9bDY8EBPpC76PGoWS_URoAR4,18412
|
|
467
|
+
bbot-2.7.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
468
|
+
bbot-2.7.2.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
|
|
469
|
+
bbot-2.7.2.dist-info/licenses/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
|
|
470
|
+
bbot-2.7.2.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
|