bbot 2.4.2.6615rc0__py3-none-any.whl → 2.4.2.6621rc0__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.4.2.6615rc"
2
+ __version__ = "v2.4.2.6621rc"
3
3
 
4
4
  from .scanner import Scanner, Preset
5
5
 
@@ -0,0 +1,75 @@
1
+ from bbot.modules.templates.github import github
2
+ from bbot.modules.templates.subdomain_enum import subdomain_enum
3
+
4
+
5
+ class github_usersearch(github, subdomain_enum):
6
+ watched_events = ["DNS_NAME"]
7
+ produced_events = ["SOCIAL", "EMAIL_ADDRESS"]
8
+ flags = ["passive", "safe", "code-enum"]
9
+ meta = {
10
+ "description": "Query Github's API for users with emails matching in scope domains that may not be discoverable by listing members of the organization.",
11
+ "created_date": "2025-05-10",
12
+ "author": "@domwhewell-sage",
13
+ "auth_required": True,
14
+ }
15
+ options = {"api_key": ""}
16
+ options_desc = {"api_key": "Github token"}
17
+
18
+ async def handle_event(self, event):
19
+ self.verbose("Searching for users with emails matching in scope domains")
20
+ query = self.make_query(event)
21
+ users = await self.query_users(query)
22
+ for user, email in users:
23
+ user_url = f"https://github.com/{user}"
24
+ event_data = {"platform": "github", "profile_name": user, "url": user_url}
25
+ user_event = self.make_event(event_data, "SOCIAL", tags="github-org-member", parent=event)
26
+ if user_event:
27
+ await self.emit_event(
28
+ user_event,
29
+ context=f"{{module}} searched for users with {{DNS_NAME}} in the profile and discovered {{event.type}}: {user_url}",
30
+ )
31
+ if email:
32
+ await self.emit_event(
33
+ email,
34
+ "EMAIL_ADDRESS",
35
+ parent=event,
36
+ context=f"{{module}} found an {{event.type}} on the github profile {user_url}: {{event.data}}",
37
+ )
38
+
39
+ async def query_users(self, query):
40
+ users = []
41
+ graphql_query = f"""query search_users {{
42
+ search(query: "{query}", type: USER, first: 100, after: "{{NEXT_KEY}}") {{
43
+ userCount
44
+ pageInfo {{
45
+ hasNextPage
46
+ endCursor
47
+ }}
48
+ edges {{
49
+ node {{
50
+ ... on User {{
51
+ login
52
+ # bio Commented out as user can add arbritrary domains to their bio
53
+ email # Email is verified by github
54
+ websiteUrl # Website is not verified by github
55
+ }}
56
+ }}
57
+ }}
58
+ }}
59
+ }}"""
60
+ async for data in self.github_graphql_request(graphql_query, "search"):
61
+ if data:
62
+ user_count = data.get("userCount", 0)
63
+ self.verbose(f"Found {user_count} users with the query {query}, verifying if they are in-scope...")
64
+ edges = data.get("edges", [])
65
+ for node in edges:
66
+ user = node.get("node", {})
67
+ in_scope_hosts = await self.scan.extract_in_scope_hostnames(str(user))
68
+ if in_scope_hosts:
69
+ login = user.get("login", "")
70
+ email = user.get("email", None)
71
+ self.verbose(
72
+ f'Found in-scope hostname(s): "{in_scope_hosts}" in the profile https://github.com/{login}, the profile appears to be in-scope'
73
+ )
74
+ users.append((login, email))
75
+ return users
@@ -44,3 +44,31 @@ class github(BaseModule):
44
44
  self.trace(traceback.format_exc())
45
45
  return None, f"Error with API ({str(e).strip()})"
46
46
  return True
47
+
48
+ async def github_graphql_request(self, graphql_query, resp_key):
49
+ url = f"{self.base_url}/graphql"
50
+ next_key = ""
51
+ has_next_page = True
52
+
53
+ while has_next_page:
54
+ query = graphql_query.replace("{NEXT_KEY}", next_key)
55
+ r = await self.api_request(url, method="POST", json={"query": query})
56
+ if r is None:
57
+ break
58
+ status_code = getattr(r, "status_code", 0)
59
+ if status_code == 403:
60
+ self.warning("Github is rate-limiting us (HTTP status: 403)")
61
+ break
62
+ try:
63
+ json = r.json()
64
+ except Exception as e:
65
+ self.warning(f"Failed to decode JSON for {r.url} (HTTP status: {status_code}): {e}")
66
+ break
67
+
68
+ data = json.get("data", {}).get(resp_key, {})
69
+ yield data
70
+
71
+ # Update pagination variables
72
+ page_info = data.get("pageInfo", {})
73
+ has_next_page = page_info.get("hasNextPage", False)
74
+ next_key = page_info.get("endCursor", "")
@@ -0,0 +1,138 @@
1
+ from .base import ModuleTestBase
2
+
3
+
4
+ class TestGithub_Usersearch(ModuleTestBase):
5
+ config_overrides = {"modules": {"github_usersearch": {"api_key": "asdf"}}}
6
+ query_1 = """query search_users {
7
+ search(query: "blacklanternsecurity.com", type: USER, first: 100, after: "") {
8
+ userCount
9
+ pageInfo {
10
+ hasNextPage
11
+ endCursor
12
+ }
13
+ edges {
14
+ node {
15
+ ... on User {
16
+ login
17
+ # bio Commented out as user can add arbritrary domains to their bio
18
+ email # Email is verified by github
19
+ websiteUrl # Website is not verified by github
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }"""
25
+ query_2 = """query search_users {
26
+ search(query: "blacklanternsecurity.com", type: USER, first: 100, after: "Y3Vyc29yOjUz") {
27
+ userCount
28
+ pageInfo {
29
+ hasNextPage
30
+ endCursor
31
+ }
32
+ edges {
33
+ node {
34
+ ... on User {
35
+ login
36
+ # bio Commented out as user can add arbritrary domains to their bio
37
+ email # Email is verified by github
38
+ websiteUrl # Website is not verified by github
39
+ }
40
+ }
41
+ }
42
+ }
43
+ }"""
44
+
45
+ async def setup_before_prep(self, module_test):
46
+ module_test.httpx_mock.add_response(url="https://api.github.com/zen")
47
+ module_test.httpx_mock.add_response(
48
+ url="https://api.github.com/graphql",
49
+ match_headers={"Authorization": "token asdf"},
50
+ match_json={"query": self.query_1},
51
+ json={
52
+ "data": {
53
+ "search": {
54
+ "userCount": 2,
55
+ "pageInfo": {"hasNextPage": True, "endCursor": "Y3Vyc29yOjUz"},
56
+ "edges": [
57
+ {
58
+ "node": {
59
+ "login": "user_one",
60
+ "email": "test@blacklanternsecurity.com",
61
+ "websiteUrl": None,
62
+ }
63
+ },
64
+ {"node": {"login": "user_two", "email": None, "websiteUrl": None}},
65
+ ],
66
+ }
67
+ }
68
+ },
69
+ )
70
+ module_test.httpx_mock.add_response(
71
+ url="https://api.github.com/graphql",
72
+ match_headers={"Authorization": "token asdf"},
73
+ match_json={"query": self.query_2},
74
+ json={
75
+ "data": {
76
+ "search": {
77
+ "userCount": 1,
78
+ "pageInfo": {"hasNextPage": False, "endCursor": "Y3Vyc29yOjU"},
79
+ "edges": [
80
+ {
81
+ "node": {
82
+ "login": "user_three",
83
+ "email": None,
84
+ "websiteUrl": "https://blog.blacklanternsecurity.com",
85
+ }
86
+ }
87
+ ],
88
+ }
89
+ }
90
+ },
91
+ )
92
+
93
+ def check(self, module_test, events):
94
+ assert 1 == len(
95
+ [
96
+ e
97
+ for e in events
98
+ if e.type == "SOCIAL"
99
+ and e.data["platform"] == "github"
100
+ and e.data["profile_name"] == "user_one"
101
+ and str(e.module) == "github_usersearch"
102
+ and "github-org-member" in e.tags
103
+ and e.scope_distance == 1
104
+ ]
105
+ ), "Failed to find user_one github"
106
+ assert 1 == len(
107
+ [
108
+ e
109
+ for e in events
110
+ if e.type == "EMAIL_ADDRESS"
111
+ and e.data == "test@blacklanternsecurity.com"
112
+ and str(e.module) == "github_usersearch"
113
+ ]
114
+ ), "Failed to find email address for user_one"
115
+ assert 0 == len(
116
+ [
117
+ e
118
+ for e in events
119
+ if e.type == "SOCIAL"
120
+ and e.data["platform"] == "github"
121
+ and e.data["profile_name"] == "user_two"
122
+ and str(e.module) == "github_usersearch"
123
+ and "github-org-member" in e.tags
124
+ and e.scope_distance == 1
125
+ ]
126
+ ), "user_two should not be in scope due to no email or website"
127
+ assert 1 == len(
128
+ [
129
+ e
130
+ for e in events
131
+ if e.type == "SOCIAL"
132
+ and e.data["platform"] == "github"
133
+ and e.data["profile_name"] == "user_three"
134
+ and str(e.module) == "github_usersearch"
135
+ and "github-org-member" in e.tags
136
+ and e.scope_distance == 1
137
+ ]
138
+ ), "Failed to find user_three github"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bbot
3
- Version: 2.4.2.6615rc0
3
+ Version: 2.4.2.6621rc0
4
4
  Summary: OSINT automation for hackers.
5
5
  License: GPL-3.0
6
6
  Keywords: python,cli,automation,osint,threat-intel,intelligence,neo4j,scanner,python-library,hacking,recursion,pentesting,recon,command-line-tool,bugbounty,subdomains,security-tools,subdomain-scanner,osint-framework,attack-surface,subdomain-enumeration,osint-tool
@@ -1,4 +1,4 @@
1
- bbot/__init__.py,sha256=WtQlPfgPAVNvoBoKj72lcIDazIAJx93c82MqqeYuhNU,163
1
+ bbot/__init__.py,sha256=DK7EeAVAAynkbWC2Q4Rv1ao4pXjaeTKcViVbLcWeK0Q,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
@@ -108,6 +108,7 @@ bbot/modules/git_clone.py,sha256=XFZXx0k97EMY3E5PZzdNvqQzZddOfRMaVp5ol2gk11s,246
108
108
  bbot/modules/gitdumper.py,sha256=XBYt6oSXm09FJVdH37zrn9T1Nhqc0zK4KLugMevedOw,11531
109
109
  bbot/modules/github_codesearch.py,sha256=a-r2vE9N9WyBpFUiKCsg0TK4Qn7DaEGyVRTUKzkDLWA,3641
110
110
  bbot/modules/github_org.py,sha256=WM18vJCHuOHJJ5rPzQzQ3Pmp7XPPuaMeVgNfW-FlO0k,8938
111
+ bbot/modules/github_usersearch.py,sha256=G8knkQBJsn7EKcMhcEaFPiB_Y5S96e2VaseBubsqOyk,3407
111
112
  bbot/modules/github_workflows.py,sha256=RDtzR0DC2sqiWzMtiqlrCSwtZHWL2MoIJBKd6LVTAdI,9720
112
113
  bbot/modules/gitlab.py,sha256=9oWWpBijeHCjuFBfWW4HvNqt7bvJvrBgBjaaz_UPPnE,5964
113
114
  bbot/modules/google_playstore.py,sha256=N4QjzQag_bgDXfX17rytBiiWA-SQtYI2N0J_ZNEOdv0,3701
@@ -199,7 +200,7 @@ bbot/modules/subdomaincenter.py,sha256=aWjcIqGGWnAj2ePwcS4sgUJDUsq0trY3Klhr_lcc4
199
200
  bbot/modules/subdomainradar.py,sha256=YlRNMtNGLpa13KZ7aksAMVZdSjxe1tkywU5RXlwXpPc,6784
200
201
  bbot/modules/telerik.py,sha256=i9Zy2JZXLrmwSslFzVFVWApQNwTwNcrGsb_UxBuCtkI,18905
201
202
  bbot/modules/templates/bucket.py,sha256=muLPpfAGtcNhL0tLU-qHTlTNIz4yncRcVjdZMqVRtUI,7153
202
- bbot/modules/templates/github.py,sha256=n6cVjf62ezkztCRAcXNnlxfCkB0VRWqn138mOOt6T08,1454
203
+ bbot/modules/templates/github.py,sha256=lrV1EYPqjtPkJsS0fQfqmLvGchNo_fO3A75W9-03gxY,2531
203
204
  bbot/modules/templates/postman.py,sha256=MIpz2q_r6LP0kIEgByo7oX5qHhMZLOhr7oKzJI9Beec,6959
204
205
  bbot/modules/templates/shodan.py,sha256=MXBvlmfw3jZFqT47v10UkqMSyQR-zBIxMJmK7PWw6uw,1174
205
206
  bbot/modules/templates/sql.py,sha256=o-CdyyoJvHJdJBKkj3CIGXYxUta4w2AB_2Vr-k7cDDU,3553
@@ -358,6 +359,7 @@ bbot/test/test_step_2/module_tests/test_module_git_clone.py,sha256=Mo0Q7bCXcrkGW
358
359
  bbot/test/test_step_2/module_tests/test_module_gitdumper.py,sha256=ya_eQUQk0344G7iqBYMls2z5H-bYM87rydbz-ACR2Ng,17461
359
360
  bbot/test/test_step_2/module_tests/test_module_github_codesearch.py,sha256=M50xBiGG2EuPGXDJU6uFsSUE-fhqZl3CzYtNdszW7LM,4735
360
361
  bbot/test/test_step_2/module_tests/test_module_github_org.py,sha256=5tKO6NH4TPBeIdeTf7Bz9PUZ1pcvKsjrG0nFhc3YgT0,25458
362
+ bbot/test/test_step_2/module_tests/test_module_github_usersearch.py,sha256=IIQ0tYZjQN8_L8u_N4m8Nz3kbB4IyBp95tYCPcQeScg,5264
361
363
  bbot/test/test_step_2/module_tests/test_module_github_workflows.py,sha256=o_teEaskm3H22QEKod5KJayFvvcgOQoG4eItGWv8C8E,38006
362
364
  bbot/test/test_step_2/module_tests/test_module_gitlab.py,sha256=fnwE7BWTU6EQquKdGLCiaX_LwVwvzOLev3Y9GheTLSY,11859
363
365
  bbot/test/test_step_2/module_tests/test_module_google_playstore.py,sha256=uTRqpAGI9HI-rOk_6jdV44OoSqi0QQQ3aTVzvuV0dtc,3034
@@ -448,8 +450,8 @@ bbot/wordlists/raft-small-extensions-lowercase_CLEANED.txt,sha256=ZSIVebs7ptMvHx
448
450
  bbot/wordlists/top_open_ports_nmap.txt,sha256=LmdFYkfapSxn1pVuQC2LkOIY2hMLgG-Xts7DVtYzweM,42727
449
451
  bbot/wordlists/valid_url_schemes.txt,sha256=0B_VAr9Dv7aYhwi6JSBDU-3M76vNtzN0qEC_RNLo7HE,3310
450
452
  bbot/wordlists/wordninja_dns.txt.gz,sha256=DYHvvfW0TvzrVwyprqODAk4tGOxv5ezNmCPSdPuDUnQ,570241
451
- bbot-2.4.2.6615rc0.dist-info/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
452
- bbot-2.4.2.6615rc0.dist-info/METADATA,sha256=zMFFr-b2zwl7kcxtDOUqz8TPuk0-KGGywvBGXnFJL90,18308
453
- bbot-2.4.2.6615rc0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
454
- bbot-2.4.2.6615rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
455
- bbot-2.4.2.6615rc0.dist-info/RECORD,,
453
+ bbot-2.4.2.6621rc0.dist-info/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
454
+ bbot-2.4.2.6621rc0.dist-info/METADATA,sha256=XpdUo5_30J3J3EJUgjM3a3VpninDPABYKWDJRVqxvqk,18308
455
+ bbot-2.4.2.6621rc0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
456
+ bbot-2.4.2.6621rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
457
+ bbot-2.4.2.6621rc0.dist-info/RECORD,,