bbot 2.4.2.6638rc0__py3-none-any.whl → 2.4.2.6655rc0__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.6638rc"
2
+ __version__ = "v2.4.2.6655rc"
3
3
 
4
4
  from .scanner import Scanner, Preset
5
5
 
@@ -0,0 +1,235 @@
1
+ import re
2
+ from bbot.modules.base import BaseModule
3
+ from bbot.errors import WordlistError
4
+
5
+
6
+ class medusa(BaseModule):
7
+ watched_events = ["PROTOCOL"]
8
+ produced_events = ["VULNERABILITY"]
9
+ flags = ["active", "aggressive", "deadly"]
10
+ per_host_only = True
11
+ meta = {
12
+ "description": "Medusa SNMP bruteforcing with v1, v2c and R/W check.",
13
+ "created_date": "2025-05-16",
14
+ "author": "@christianfl",
15
+ }
16
+ scope_distance_modifier = None
17
+
18
+ options = {
19
+ "snmp_wordlist": "https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Discovery/SNMP/common-snmp-community-strings.txt",
20
+ "snmp_versions": ["1", "2C"], # Only 1 and 2C are available with medusa 2.3.
21
+ "wait_microseconds": 200,
22
+ "timeout_s": 5,
23
+ "threads": 5,
24
+ }
25
+
26
+ options_desc = {
27
+ "snmp_wordlist": "Wordlist url for SNMP community strings, newline separated (default https://raw.githubusercontent.com/danielmiessler/SecLists/refs/heads/master/Discovery/SNMP/snmp.txt)",
28
+ "snmp_versions": "List of SNMP versions to attempt against the SNMP server (default ['1', '2C'])",
29
+ "wait_microseconds": "Wait time after every SNMP request in microseconds (default 200)",
30
+ "timeout_s": "Wait time for the SNMP response(s) once at the end of all attempts (default 5)",
31
+ "threads": "Number of communities to be tested concurrently (default 5)",
32
+ }
33
+
34
+ deps_ansible = [
35
+ {
36
+ "name": "Install build dependencies",
37
+ "package": {
38
+ "name": [
39
+ "autoconf",
40
+ "automake",
41
+ "libtool",
42
+ "gcc",
43
+ "make",
44
+ ],
45
+ "state": "present",
46
+ },
47
+ "become": True,
48
+ "ignore_errors": True,
49
+ },
50
+ {
51
+ "name": "Get medusa repo",
52
+ "git": {
53
+ "repo": "https://github.com/jmk-foofus/medusa",
54
+ "dest": "#{BBOT_TEMP}/medusa/gitrepo",
55
+ "version": "2.3", # Newest stable, 2025-05-15
56
+ },
57
+ },
58
+ {
59
+ # The git repo will be copied because during build, files and subfolders get created. That prevents the Ansible git module to cache the repo.
60
+ "name": "Copy medusa repo",
61
+ "copy": {
62
+ "src": "#{BBOT_TEMP}/medusa/gitrepo/",
63
+ "dest": "#{BBOT_TEMP}/medusa/workdir/",
64
+ },
65
+ },
66
+ {
67
+ "name": "Build medusa: autoreconf",
68
+ "command": {
69
+ "chdir": "#{BBOT_TEMP}/medusa/workdir",
70
+ "cmd": "autoreconf -f -i",
71
+ },
72
+ },
73
+ {
74
+ "name": "Build medusa: configure",
75
+ "command": {
76
+ "chdir": "#{BBOT_TEMP}/medusa/workdir",
77
+ "cmd": "./configure --prefix=#{BBOT_TEMP}/medusa/build",
78
+ },
79
+ },
80
+ {
81
+ "name": "Build medusa: make",
82
+ "command": {
83
+ "chdir": "#{BBOT_TEMP}/medusa/workdir",
84
+ "cmd": "make",
85
+ },
86
+ },
87
+ {
88
+ "name": "Build medusa: make install",
89
+ "command": {
90
+ "chdir": "#{BBOT_TEMP}/medusa/workdir",
91
+ "cmd": "make install",
92
+ "creates": "#{BBOT_TEMP}/medusa/build/bin/medusa",
93
+ },
94
+ },
95
+ {
96
+ "name": "Install medusa",
97
+ "copy": {
98
+ "src": "#{BBOT_TEMP}/medusa/build/bin/medusa",
99
+ "dest": "#{BBOT_TOOLS}/",
100
+ "mode": "u+x,g+x,o+x",
101
+ },
102
+ },
103
+ ]
104
+
105
+ async def setup(self):
106
+ # Try to cache wordlist
107
+ try:
108
+ self.snmp_wordlist_path = await self.helpers.wordlist(self.config.get("snmp_wordlist"))
109
+ except WordlistError as e:
110
+ return False, f"Error retrieving wordlist: {e}"
111
+
112
+ self.password_match_regex = re.compile(r"Password:\s*(\S+)")
113
+ self.success_indicator_match_regex = re.compile(r"\[([^\]]+)\]\s*$")
114
+
115
+ return True
116
+
117
+ async def filter_event(self, event):
118
+ handled_protocols = ["snmp"] # Could be extended later
119
+
120
+ protocol = event.data["protocol"].lower()
121
+ if not protocol in handled_protocols:
122
+ return False, f"service {protocol} is currently not supported. Only SNMP."
123
+
124
+ return True
125
+
126
+ async def handle_event(self, event):
127
+ host = str(event.host)
128
+ port = str(event.port)
129
+ protocol = event.data["protocol"].lower()
130
+
131
+ if protocol == "snmp":
132
+ snmp_versions = self.config.get("snmp_versions")
133
+
134
+ # Medusa must be called for each SNMP version separately after each run finished.
135
+ for snmp_version in snmp_versions:
136
+ command = await self.construct_command(host, port, protocol, snmp_version)
137
+
138
+ result = await self.run_process(command)
139
+
140
+ if result.stderr:
141
+ # Medusa outputs to stderr if a readonly community was found in WRITE mode
142
+ # That's intended behavior
143
+ self.info(f"Medusa stderr: {result.stderr}")
144
+
145
+ async for message in self.parse_output(result.stdout, snmp_version):
146
+ vuln_event = self.create_vuln_event("CRITICAL", message, event)
147
+ await self.emit_event(vuln_event)
148
+
149
+ # else: Medusa supports various protocols which could in theory be implemented later on.
150
+
151
+ async def parse_output(self, output, protocol_version):
152
+ for line in output.splitlines():
153
+ # Print original Medusa output
154
+ self.info(line)
155
+
156
+ if "FOUND" in line:
157
+ # Some credential was guessed
158
+ password_match = self.password_match_regex.search(line)
159
+ password = password_match.group(1) if password_match else None
160
+
161
+ success_indicator_match = self.success_indicator_match_regex.search(line)
162
+ success_indicator = success_indicator_match.group(1) if success_indicator_match else None
163
+
164
+ # Medusa in WRITE mode shows "ERROR" if a readonly community was found. Replace with "READ"
165
+ mode = "R/W" if success_indicator == "success" else "READ" if success_indicator == "ERROR" else "MODE?"
166
+
167
+ message = f"VALID [SNMPV{protocol_version}] CREDENTIALS FOUND: {password} [{mode}]"
168
+
169
+ yield message
170
+
171
+ async def construct_command(self, host, port, protocol, protocol_version):
172
+ # -b Suppress startup banner
173
+ # -v Set verbosity level (4 = Show only errors and credentials)
174
+ # -R Number of attempted retries
175
+ # -M Medusa module to execute (SNMP)
176
+ # -T Number of concurrent hosts
177
+ # -t Number of concurrent login attempts
178
+ # -h Target hostname or ip address
179
+ # -u Username to test (Empty for SNMP)
180
+ # -P Wordlist for passwords
181
+ # -m Module specific parameters:
182
+ # TIMEOUT:<number> Sets the number of seconds to wait for the UDP responses (default: 5 sec).
183
+ # SEND_DELAY:<number> Sets the number of microseconds to wait between sending queries (default: 200 usec).
184
+ # VERSION:<1|2C> Set the SNMP client version.
185
+ # ACCESS:<READ|WRITE> Set level of access to test for with the community string. ("WRITE" does include "READ")
186
+
187
+ # Example command to bruteforce SNMP:
188
+ #
189
+ # medusa -b -v 4 -R 1 -M snmp -T 1 -t 1 -h 127.0.0.1 -u '' -P communities.txt -m VERSION:2C -m SEND_DELAY:1000000 -m ACCESS:WRITE -m TIMEOUT:10
190
+
191
+ cmd = [
192
+ "medusa",
193
+ "-b",
194
+ "-v",
195
+ 4,
196
+ "-R",
197
+ 1,
198
+ "-M",
199
+ protocol,
200
+ "-T",
201
+ 1,
202
+ "-t",
203
+ self.config.get("threads"),
204
+ "-h",
205
+ host,
206
+ "-u",
207
+ "''",
208
+ "-P",
209
+ self.snmp_wordlist_path,
210
+ "-m",
211
+ f"VERSION:{protocol_version}",
212
+ "-m",
213
+ f"SEND_DELAY:{self.config.get('wait_microseconds')}",
214
+ "-m",
215
+ "ACCESS:WRITE",
216
+ "-m",
217
+ f"TIMEOUT:{self.config.get('timeout_s')}",
218
+ ]
219
+
220
+ return cmd
221
+
222
+ def create_vuln_event(self, severity, description, source_event):
223
+ host = str(source_event.host)
224
+ port = str(source_event.port)
225
+
226
+ return self.make_event(
227
+ {
228
+ "severity": severity,
229
+ "host": host,
230
+ "port": port,
231
+ "description": description,
232
+ },
233
+ "VULNERABILITY",
234
+ source_event,
235
+ )
@@ -0,0 +1,50 @@
1
+ from .base import ModuleTestBase, tempwordlist
2
+ import pytest
3
+
4
+
5
+ @pytest.fixture
6
+ def mock_medusa_run_process(monkeypatch):
7
+ async def fake_run_process(self, cmd):
8
+ class FakeResult:
9
+ stdout = "ACCOUNT FOUND: [snmp] Host: 127.0.0.1 User: (null) Password: public [ERROR]\n"
10
+ stderr = (
11
+ "ERROR: [snmp.mod] Error processing SNMP response (1).\n"
12
+ "ERROR: [snmp.mod] Community string appears to have only READ access.\n"
13
+ )
14
+
15
+ return FakeResult()
16
+
17
+ from bbot.modules.base import BaseModule
18
+
19
+ monkeypatch.setattr(BaseModule, "run_process", fake_run_process)
20
+
21
+
22
+ @pytest.mark.usefixtures("mock_medusa_run_process")
23
+ class TestMedusa(ModuleTestBase):
24
+ targets = ["127.0.0.1"]
25
+ temp_snmp_wordlist = tempwordlist(["public", "private, admin"])
26
+ config_overrides = {
27
+ "modules": {
28
+ "medusa": {
29
+ "snmp_versions": ["2C"],
30
+ "timeout_s": 1,
31
+ "snmp_wordlist": str(temp_snmp_wordlist),
32
+ }
33
+ }
34
+ }
35
+
36
+ async def setup_after_prep(self, module_test):
37
+ protocol_data = {"host": str(self.targets[0]), "protocol": "snmp", "port": 161}
38
+
39
+ protocol_event = module_test.scan.make_event(
40
+ protocol_data,
41
+ "PROTOCOL",
42
+ parent=module_test.scan.root_event,
43
+ )
44
+ await module_test.module.emit_event(protocol_event)
45
+
46
+ def check(self, module_test, events):
47
+ vuln_events = [e for e in events if e.type == "VULNERABILITY"]
48
+
49
+ assert len(vuln_events) == 1
50
+ assert "VALID [SNMPV2C] CREDENTIALS FOUND: public [READ]" in vuln_events[0].data["description"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bbot
3
- Version: 2.4.2.6638rc0
3
+ Version: 2.4.2.6655rc0
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=MKxhXFa7Z1IH-IYRMco6EJRNAHZvMyIhFC_T-5pCQV0,163
1
+ bbot/__init__.py,sha256=yDRP6RswzftH1j2TWgwfKcvWyZmu1jV25g7RRdk5lj0,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,6 +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/deadly/medusa.py,sha256=44psluRg9VFo6WNLKlnTtH66afqhrOF0STBbLhFkHCE,8859
86
87
  bbot/modules/dehashed.py,sha256=0lzcqMEgwRmprwurZ2-8Y8aOO4KTueJgpY_vh0DWQwA,5155
87
88
  bbot/modules/digitorus.py,sha256=XQY0eAQrA7yo8S57tGncP1ARud-yG4LiWxx5VBYID34,1027
88
89
  bbot/modules/dnsbimi.py,sha256=A4cqhvhytmEEd-tY4CgFwMLbsVtMjkRY9238Aj8aVtU,6921
@@ -378,6 +379,7 @@ bbot/test/test_step_2/module_tests/test_module_jadx.py,sha256=qTBfDc_Iv03n8iGdyL
378
379
  bbot/test/test_step_2/module_tests/test_module_json.py,sha256=gmlqge5ZJpjVMGs7OLZBsNlSFTTrKnKjIZMIU23o8VQ,3350
379
380
  bbot/test/test_step_2/module_tests/test_module_leakix.py,sha256=DQaQsL4ewpuYeygp-sgcvdeOSzvHq77_eYjKcgebS7A,1817
380
381
  bbot/test/test_step_2/module_tests/test_module_lightfuzz.py,sha256=g8rPTtjPe90ZkjCEMlNUC2fraqzZu4XK_0GaA7sGI9A,77463
382
+ bbot/test/test_step_2/module_tests/test_module_medusa.py,sha256=vYoAyMf0LbIXCoUzLycOISZtF7M58E30WjuLuqxDiCg,1671
381
383
  bbot/test/test_step_2/module_tests/test_module_mysql.py,sha256=4wAPjbjhlxmOkEhQnIQIBC2BLEaE57TX6lChGZ3zLsU,2630
382
384
  bbot/test/test_step_2/module_tests/test_module_myssl.py,sha256=zRJ1sOEespWtBx2jA07bW5sHD1XQ9pV0PtHtGogo7Gs,1531
383
385
  bbot/test/test_step_2/module_tests/test_module_neo4j.py,sha256=pUUaqxBsF6s11dEDhrETpvlR2pqiUcc0uvH8Z5GvVUQ,1332
@@ -450,8 +452,8 @@ bbot/wordlists/raft-small-extensions-lowercase_CLEANED.txt,sha256=ZSIVebs7ptMvHx
450
452
  bbot/wordlists/top_open_ports_nmap.txt,sha256=LmdFYkfapSxn1pVuQC2LkOIY2hMLgG-Xts7DVtYzweM,42727
451
453
  bbot/wordlists/valid_url_schemes.txt,sha256=0B_VAr9Dv7aYhwi6JSBDU-3M76vNtzN0qEC_RNLo7HE,3310
452
454
  bbot/wordlists/wordninja_dns.txt.gz,sha256=DYHvvfW0TvzrVwyprqODAk4tGOxv5ezNmCPSdPuDUnQ,570241
453
- bbot-2.4.2.6638rc0.dist-info/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
454
- bbot-2.4.2.6638rc0.dist-info/METADATA,sha256=vHgtJRTbiaQgj8CWEdh7mMF-gHzb85Xrr2jSUbFqjKc,18308
455
- bbot-2.4.2.6638rc0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
456
- bbot-2.4.2.6638rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
457
- bbot-2.4.2.6638rc0.dist-info/RECORD,,
455
+ bbot-2.4.2.6655rc0.dist-info/LICENSE,sha256=GzeCzK17hhQQDNow0_r0L8OfLpeTKQjFQwBQU7ZUymg,32473
456
+ bbot-2.4.2.6655rc0.dist-info/METADATA,sha256=ZGC7qPJcqpTX0gaUp2rLUa6oIi9Qzxex27HjI0cGFmg,18308
457
+ bbot-2.4.2.6655rc0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
458
+ bbot-2.4.2.6655rc0.dist-info/entry_points.txt,sha256=cWjvcU_lLrzzJgjcjF7yeGuRA_eDS8pQ-kmPUAyOBfo,38
459
+ bbot-2.4.2.6655rc0.dist-info/RECORD,,