bbot 2.7.1.7212rc0__py3-none-any.whl → 2.7.2.7226rc0__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.7226rc"
3
3
 
4
4
  from .scanner import Scanner, Preset
5
5
 
@@ -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 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.7226rc0
4
4
  Summary: OSINT automation for hackers.
5
5
  License: GPL-3.0
6
6
  License-File: LICENSE
@@ -1,4 +1,4 @@
1
- bbot/__init__.py,sha256=VSc_ku85NcucmahEOo7gG1JKRDfMFITM_ihdRv4OvPs,163
1
+ bbot/__init__.py,sha256=AeiAD6xDDTkFX7riGfY4k07bFewqIBQ-B1hpl7KWtKE,163
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/gitlab.py,sha256=9oWWpBijeHCjuFBfWW4HvNqt7bvJvrBgBjaaz_UPPnE,5964
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/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.7226rc0.dist-info/METADATA,sha256=x8WuBr4-IFgVj7k89QiL3i-rvB48oYwAjg-zWGiSNjU,18420
467
+ bbot-2.7.2.7226rc0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
468
+ bbot-2.7.2.7226rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
469
+ bbot-2.7.2.7226rc0.dist-info/licenses/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
470
+ bbot-2.7.2.7226rc0.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()