bbot 2.4.2.6611rc0__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 +1 -1
- bbot/modules/dehashed.py +44 -39
- bbot/modules/github_usersearch.py +75 -0
- bbot/modules/templates/github.py +28 -0
- bbot/test/test_step_2/module_tests/test_module_dehashed.py +95 -38
- bbot/test/test_step_2/module_tests/test_module_github_usersearch.py +138 -0
- {bbot-2.4.2.6611rc0.dist-info → bbot-2.4.2.6621rc0.dist-info}/METADATA +1 -1
- {bbot-2.4.2.6611rc0.dist-info → bbot-2.4.2.6621rc0.dist-info}/RECORD +11 -9
- {bbot-2.4.2.6611rc0.dist-info → bbot-2.4.2.6621rc0.dist-info}/WHEEL +1 -1
- {bbot-2.4.2.6611rc0.dist-info → bbot-2.4.2.6621rc0.dist-info}/LICENSE +0 -0
- {bbot-2.4.2.6611rc0.dist-info → bbot-2.4.2.6621rc0.dist-info}/entry_points.txt +0 -0
bbot/__init__.py
CHANGED
bbot/modules/dehashed.py
CHANGED
|
@@ -13,23 +13,23 @@ class dehashed(subdomain_enum):
|
|
|
13
13
|
"author": "@SpamFaux",
|
|
14
14
|
"auth_required": True,
|
|
15
15
|
}
|
|
16
|
-
options = {"
|
|
17
|
-
options_desc = {"
|
|
16
|
+
options = {"api_key": ""}
|
|
17
|
+
options_desc = {"api_key": "DeHashed API Key"}
|
|
18
18
|
target_only = True
|
|
19
19
|
|
|
20
|
-
base_url = "https://api.dehashed.com/search"
|
|
20
|
+
base_url = "https://api.dehashed.com/v2/search"
|
|
21
21
|
|
|
22
22
|
async def setup(self):
|
|
23
|
-
self.username = self.config.get("username", "")
|
|
24
23
|
self.api_key = self.config.get("api_key", "")
|
|
25
|
-
self.auth = (self.username, self.api_key)
|
|
26
24
|
self.headers = {
|
|
27
25
|
"Accept": "application/json",
|
|
26
|
+
"Content-Type": "application/json",
|
|
27
|
+
"Dehashed-Api-Key": self.api_key,
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
# soft-fail if we don't have the necessary information to make queries
|
|
31
|
-
if not
|
|
32
|
-
return None, "No
|
|
31
|
+
if not self.api_key:
|
|
32
|
+
return None, "No API key set"
|
|
33
33
|
|
|
34
34
|
return await super().setup()
|
|
35
35
|
|
|
@@ -38,29 +38,31 @@ class dehashed(subdomain_enum):
|
|
|
38
38
|
async for entries in self.query(query):
|
|
39
39
|
for entry in entries:
|
|
40
40
|
# we have to clean up the email field because dehashed does a poor job of it
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
emails = []
|
|
42
|
+
for email in entry.get("email", []):
|
|
43
|
+
email_str = email.replace("\\", "")
|
|
44
|
+
found_emails = list(await self.helpers.re.extract_emails(email_str))
|
|
45
|
+
if not found_emails:
|
|
46
|
+
self.debug(f"Invalid email from dehashed.com: {email_str}")
|
|
47
|
+
continue
|
|
48
|
+
emails += found_emails
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
users = entry.get("username", [])
|
|
51
|
+
pws = entry.get("password", [])
|
|
52
|
+
h_pws = entry.get("hashed_password", [])
|
|
51
53
|
db_name = entry.get("database_name", "")
|
|
52
54
|
|
|
53
55
|
tags = []
|
|
54
56
|
if db_name:
|
|
55
57
|
tags = [f"db-{db_name}"]
|
|
56
|
-
|
|
58
|
+
for email in emails:
|
|
57
59
|
email_event = self.make_event(email, "EMAIL_ADDRESS", parent=event, tags=tags)
|
|
58
60
|
if email_event is not None:
|
|
59
61
|
await self.emit_event(
|
|
60
62
|
email_event,
|
|
61
63
|
context=f'{{module}} searched API for "{query}" and found {{event.type}}: {{event.data}}',
|
|
62
64
|
)
|
|
63
|
-
|
|
65
|
+
for user in users:
|
|
64
66
|
await self.emit_event(
|
|
65
67
|
f"{email}:{user}",
|
|
66
68
|
"USERNAME",
|
|
@@ -68,7 +70,7 @@ class dehashed(subdomain_enum):
|
|
|
68
70
|
tags=tags,
|
|
69
71
|
context=f"{{module}} found {email} with {{event.type}}: {{event.data}}",
|
|
70
72
|
)
|
|
71
|
-
|
|
73
|
+
for pw in pws:
|
|
72
74
|
await self.emit_event(
|
|
73
75
|
f"{email}:{pw}",
|
|
74
76
|
"PASSWORD",
|
|
@@ -76,7 +78,7 @@ class dehashed(subdomain_enum):
|
|
|
76
78
|
tags=tags,
|
|
77
79
|
context=f"{{module}} found {email} with {{event.type}}: {{event.data}}",
|
|
78
80
|
)
|
|
79
|
-
|
|
81
|
+
for h_pw in h_pws:
|
|
80
82
|
await self.emit_event(
|
|
81
83
|
f"{email}:{h_pw}",
|
|
82
84
|
"HASHED_PASSWORD",
|
|
@@ -86,30 +88,33 @@ class dehashed(subdomain_enum):
|
|
|
86
88
|
)
|
|
87
89
|
|
|
88
90
|
async def query(self, domain):
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
url = self.base_url
|
|
92
|
+
json = {
|
|
93
|
+
"query": "",
|
|
94
|
+
"page": 1,
|
|
95
|
+
"size": 10000, # The maximum permitted size and pagination.
|
|
96
|
+
}
|
|
97
|
+
json["query"] = f"domain:{domain}"
|
|
98
|
+
json["page"] = 1
|
|
99
|
+
max_pages = 1
|
|
100
|
+
agen = self.api_page_iter(url=url, headers=self.headers, _json=False, method="POST", json=json)
|
|
94
101
|
async for result in agen:
|
|
95
102
|
result_json = {}
|
|
96
103
|
with suppress(Exception):
|
|
97
104
|
result_json = result.json()
|
|
98
105
|
total = result_json.get("total", 0)
|
|
99
106
|
entries = result_json.get("entries", [])
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
)
|
|
107
|
+
json["page"] += 1
|
|
108
|
+
if result is not None and result.status_code != 200:
|
|
109
|
+
self.warning(
|
|
110
|
+
f"Error retrieving results from dehashed.com (status code {result.status_code}): {result.text}"
|
|
111
|
+
)
|
|
112
|
+
elif (json["page"] > max_pages) and (total > (json["size"] * max_pages)):
|
|
113
|
+
self.info(
|
|
114
|
+
f"{domain} has {total:,} results in Dehashed. The API can only process the first 10,000 results. Please check dehashed.com to get the remaining results."
|
|
115
|
+
)
|
|
116
|
+
if entries:
|
|
117
|
+
yield entries
|
|
118
|
+
if not entries or json["page"] > max_pages:
|
|
113
119
|
await agen.aclose()
|
|
114
120
|
break
|
|
115
|
-
yield entries
|
|
@@ -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", "")
|
|
@@ -1,52 +1,44 @@
|
|
|
1
1
|
from .base import ModuleTestBase
|
|
2
2
|
|
|
3
|
-
dehashed_domain_response = {
|
|
4
|
-
"balance": 10000,
|
|
5
|
-
"entries": [
|
|
6
|
-
{
|
|
7
|
-
"id": "4363462346",
|
|
8
|
-
"email": "bob@blacklanternsecurity.com",
|
|
9
|
-
"ip_address": "",
|
|
10
|
-
"username": "bob@bob.com",
|
|
11
|
-
"password": "",
|
|
12
|
-
"hashed_password": "$2a$12$pVmwJ7pXEr3mE.DmCCE4fOUDdeadbeefd2KuCy/tq1ZUFyEOH2bve",
|
|
13
|
-
"name": "Bob Smith",
|
|
14
|
-
"vin": "",
|
|
15
|
-
"address": "",
|
|
16
|
-
"phone": "+91283423839",
|
|
17
|
-
"database_name": "eatstreet",
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"id": "234623453454",
|
|
21
|
-
"email": "tim@blacklanternsecurity.com",
|
|
22
|
-
"ip_address": "",
|
|
23
|
-
"username": "timmy",
|
|
24
|
-
"password": "TimTamSlam69",
|
|
25
|
-
"hashed_password": "",
|
|
26
|
-
"name": "Tim Tam",
|
|
27
|
-
"vin": "",
|
|
28
|
-
"address": "",
|
|
29
|
-
"phone": "+123455667",
|
|
30
|
-
"database_name": "eatstreet",
|
|
31
|
-
},
|
|
32
|
-
],
|
|
33
|
-
"success": True,
|
|
34
|
-
"took": "61µs",
|
|
35
|
-
"total": 2,
|
|
36
|
-
}
|
|
37
|
-
|
|
38
3
|
|
|
39
4
|
class TestDehashed(ModuleTestBase):
|
|
40
5
|
modules_overrides = ["dehashed", "speculate"]
|
|
41
6
|
config_overrides = {
|
|
42
7
|
"scope": {"report_distance": 2},
|
|
43
|
-
"modules": {"dehashed": {"
|
|
8
|
+
"modules": {"dehashed": {"api_key": "deadbeef"}},
|
|
44
9
|
}
|
|
45
10
|
|
|
46
11
|
async def setup_before_prep(self, module_test):
|
|
47
12
|
module_test.httpx_mock.add_response(
|
|
48
|
-
url="https://api.dehashed.com/search
|
|
49
|
-
|
|
13
|
+
url="https://api.dehashed.com/v2/search",
|
|
14
|
+
method="POST",
|
|
15
|
+
json={
|
|
16
|
+
"balance": 10000,
|
|
17
|
+
"entries": [
|
|
18
|
+
{
|
|
19
|
+
"id": "4363462346",
|
|
20
|
+
"email": ["bob@blacklanternsecurity.com"],
|
|
21
|
+
"ip_address": ["127.0.0.9"],
|
|
22
|
+
"username": ["bob@bob.com"],
|
|
23
|
+
"hashed_password": ["$2a$12$pVmwJ7pXEr3mE.DmCCE4fOUDdeadbeefd2KuCy/tq1ZUFyEOH2bve"],
|
|
24
|
+
"name": ["Bob Smith"],
|
|
25
|
+
"phone": ["+91283423839"],
|
|
26
|
+
"database_name": "eatstreet",
|
|
27
|
+
"raw_record": {"le_only": True, "unstructured": True},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "234623453454",
|
|
31
|
+
"email": ["tim@blacklanternsecurity.com"],
|
|
32
|
+
"username": ["timmy"],
|
|
33
|
+
"password": ["TimTamSlam69"],
|
|
34
|
+
"name": "Tim Tam",
|
|
35
|
+
"phone": ["+123455667"],
|
|
36
|
+
"database_name": "eatstreet",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
"took": "61ms",
|
|
40
|
+
"total": 2,
|
|
41
|
+
},
|
|
50
42
|
)
|
|
51
43
|
await module_test.mock_dns(
|
|
52
44
|
{
|
|
@@ -100,3 +92,68 @@ class TestDehashed(ModuleTestBase):
|
|
|
100
92
|
[e for e in events if e.type == "PASSWORD" and e.data == "tim@blacklanternsecurity.com:TimTamSlam69"]
|
|
101
93
|
)
|
|
102
94
|
assert 1 == len([e for e in events if e.type == "USERNAME" and e.data == "tim@blacklanternsecurity.com:timmy"])
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TestDehashedBadEmail(TestDehashed):
|
|
98
|
+
async def setup_before_prep(self, module_test):
|
|
99
|
+
module_test.httpx_mock.add_response(
|
|
100
|
+
url="https://api.dehashed.com/v2/search",
|
|
101
|
+
method="POST",
|
|
102
|
+
json={
|
|
103
|
+
"balance": 10000,
|
|
104
|
+
"entries": [
|
|
105
|
+
{
|
|
106
|
+
"id": "EZxg4Lz-INLUt6uRXZaV",
|
|
107
|
+
"email": ["foo.example.com"],
|
|
108
|
+
"database_name": "Collections",
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
"took": "41ms",
|
|
112
|
+
"total": 1,
|
|
113
|
+
},
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def check(self, module_test, events):
|
|
117
|
+
debug_log_content = open(module_test.scan.home / "debug.log").read()
|
|
118
|
+
assert "Invalid email from dehashed.com: foo.example.com" in debug_log_content
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class TestDehashedHTTPError(TestDehashed):
|
|
122
|
+
async def setup_before_prep(self, module_test):
|
|
123
|
+
module_test.httpx_mock.add_response(
|
|
124
|
+
url="https://api.dehashed.com/v2/search",
|
|
125
|
+
method="POST",
|
|
126
|
+
json={"error": "issue with request body"},
|
|
127
|
+
status_code=400,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def check(self, module_test, events):
|
|
131
|
+
scan_log_content = open(module_test.scan.home / "scan.log").read()
|
|
132
|
+
assert (
|
|
133
|
+
'Error retrieving results from dehashed.com (status code 400): {"error":"issue with request body"}'
|
|
134
|
+
in scan_log_content
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TestDehashedTooManyResults(TestDehashed):
|
|
139
|
+
async def setup_before_prep(self, module_test):
|
|
140
|
+
module_test.httpx_mock.add_response(
|
|
141
|
+
url="https://api.dehashed.com/v2/search",
|
|
142
|
+
method="POST",
|
|
143
|
+
json={
|
|
144
|
+
"balance": 10000,
|
|
145
|
+
"entries": [
|
|
146
|
+
{
|
|
147
|
+
"id": "VXhNxj46SGsW4Lworh-G",
|
|
148
|
+
"email": ["bob@bob.com"],
|
|
149
|
+
"database_name": "Collections",
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
"took": "40ms",
|
|
153
|
+
"total": 10001,
|
|
154
|
+
},
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def check(self, module_test, events):
|
|
158
|
+
scan_log_content = open(module_test.scan.home / "scan.log").read()
|
|
159
|
+
assert "has 10,001 results in Dehashed. The API can only process the first 10,000 results." in scan_log_content
|
|
@@ -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.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=
|
|
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
|
|
@@ -83,7 +83,7 @@ bbot/modules/code_repository.py,sha256=x70Z45VnNNMF8BPkHfGWZXsZXw_fStGB3y0-8jbP1
|
|
|
83
83
|
bbot/modules/credshed.py,sha256=HAF5wgRGKIIpdMAe4mIAtkZRLmFYjMFyXtjjst6RJ20,4203
|
|
84
84
|
bbot/modules/crt.py,sha256=6Zm90VKXwYYN6Sab0gwwhTARrtnQIqALJTVtFWMMTGk,1369
|
|
85
85
|
bbot/modules/crt_db.py,sha256=xaIm2457_xGJjnKss73l1HpPn7pLPHksVzejsimTfZA,2198
|
|
86
|
-
bbot/modules/dehashed.py,sha256=
|
|
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=A4cqhvhytmEEd-tY4CgFwMLbsVtMjkRY9238Aj8aVtU,6921
|
|
89
89
|
bbot/modules/dnsbrute.py,sha256=Y2bSbG2IcwIJID1FSQ6Qe9fdpWwG7GIO-wVQw7MdQFM,2439
|
|
@@ -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
|
|
@@ -329,7 +330,7 @@ bbot/test/test_step_2/module_tests/test_module_credshed.py,sha256=ipkCFL7YmZBLWW
|
|
|
329
330
|
bbot/test/test_step_2/module_tests/test_module_crt.py,sha256=V15tE1jcXdXJEzEEdAJvSMRWhKBFtxBBUJ_eewvV3U4,717
|
|
330
331
|
bbot/test/test_step_2/module_tests/test_module_crt_db.py,sha256=R0CvLnzhN5T7XNPVSDRYD9vSwYMTNED6w-EPGoWPVII,849
|
|
331
332
|
bbot/test/test_step_2/module_tests/test_module_csv.py,sha256=UJqMqdiPjx-UjJw10OoVMAj378wu5mWIq0v04TCljTM,579
|
|
332
|
-
bbot/test/test_step_2/module_tests/test_module_dehashed.py,sha256=
|
|
333
|
+
bbot/test/test_step_2/module_tests/test_module_dehashed.py,sha256=Oi2McmPf873xWWUahYNh-CquDfLFamATyODjAZL33vU,6009
|
|
333
334
|
bbot/test/test_step_2/module_tests/test_module_digitorus.py,sha256=1GwxQGny6TxHsV8Fx7cR-aaLU8ZZkcF065VM_XoG1Hs,1612
|
|
334
335
|
bbot/test/test_step_2/module_tests/test_module_discord.py,sha256=Z66fGb-kkdZTQfUh6WZiM35Ad-gDyvwxlA7mUUB2vnQ,1838
|
|
335
336
|
bbot/test/test_step_2/module_tests/test_module_dnsbimi.py,sha256=nAhNAuH2hS7r4KYzhuPMocae1ifoIRMESYio8L577lg,4393
|
|
@@ -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.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,,
|
|
File without changes
|
|
File without changes
|