bbot 2.4.2.6615rc0__py3-none-any.whl → 2.4.2.6623rc0__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/github_usersearch.py +75 -0
- bbot/modules/templates/github.py +28 -0
- bbot/test/test_step_2/module_tests/test_module_github_usersearch.py +138 -0
- {bbot-2.4.2.6615rc0.dist-info → bbot-2.4.2.6623rc0.dist-info}/METADATA +1 -1
- {bbot-2.4.2.6615rc0.dist-info → bbot-2.4.2.6623rc0.dist-info}/RECORD +9 -7
- {bbot-2.4.2.6615rc0.dist-info → bbot-2.4.2.6623rc0.dist-info}/LICENSE +0 -0
- {bbot-2.4.2.6615rc0.dist-info → bbot-2.4.2.6623rc0.dist-info}/WHEEL +0 -0
- {bbot-2.4.2.6615rc0.dist-info → bbot-2.4.2.6623rc0.dist-info}/entry_points.txt +0 -0
bbot/__init__.py
CHANGED
|
@@ -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
|
bbot/modules/templates/github.py
CHANGED
|
@@ -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.
|
|
3
|
+
Version: 2.4.2.6623rc0
|
|
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=
|
|
1
|
+
bbot/__init__.py,sha256=k0pwYL_4Erj2fgs_7ngRan2CpJgelJDddDUSs2EcqkE,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=
|
|
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.
|
|
452
|
-
bbot-2.4.2.
|
|
453
|
-
bbot-2.4.2.
|
|
454
|
-
bbot-2.4.2.
|
|
455
|
-
bbot-2.4.2.
|
|
453
|
+
bbot-2.4.2.6623rc0.dist-info/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
|
|
454
|
+
bbot-2.4.2.6623rc0.dist-info/METADATA,sha256=OUGmmKHKf4zBdtAG1oxDolDrlqVvSyQ8TDY1M7m6IDU,18308
|
|
455
|
+
bbot-2.4.2.6623rc0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
|
456
|
+
bbot-2.4.2.6623rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
|
|
457
|
+
bbot-2.4.2.6623rc0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|