Skip to content

Commit 9de96b6

Browse files
Merge pull request #3068 from TrebledJ/patch-2
enhancement: in fingerprintx, emit `URL_UNVERIFIED` event upon detecting http protocols
2 parents 1f93c7a + eb4c79a commit 9de96b6

2 files changed

Lines changed: 68 additions & 2 deletions

File tree

bbot/modules/fingerprintx.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
class fingerprintx(BaseModule):
77
watched_events = ["OPEN_TCP_PORT"]
8-
produced_events = ["PROTOCOL"]
8+
produced_events = ["PROTOCOL", "URL_UNVERIFIED"]
99
flags = ["safe", "active", "service-enum", "slow"]
1010
meta = {
1111
"description": "Fingerprint exposed services like RDP, SSH, MySQL, etc.",
@@ -78,7 +78,7 @@ async def handle_batch(self, *events):
7878
if not host and port and protocol:
7979
continue
8080
banner = j.get("metadata", {}).get("banner", "").strip()
81-
port_data = f"{host}:{port}"
81+
port_data = self.helpers.make_netloc(host, port)
8282
tags = set()
8383
parent_event = _input.get(port_data)
8484
protocol_data = {"host": host, "protocol": protocol}
@@ -93,3 +93,15 @@ async def handle_batch(self, *events):
9393
tags=tags,
9494
context=f"{{module}} probed {port_data} and detected {{event.type}}: {protocol}",
9595
)
96+
if protocol in ("HTTP", "HTTPS"):
97+
port_int = int(port) if port else None
98+
is_default_port = (protocol == "HTTP" and port_int == 80) or (protocol == "HTTPS" and port_int == 443)
99+
netloc = self.helpers.make_netloc(host, None if is_default_port else port_int)
100+
url = f"{protocol.lower()}://{netloc}"
101+
await self.emit_event(
102+
url,
103+
"URL_UNVERIFIED",
104+
parent=parent_event,
105+
tags=tags,
106+
context=f"{{module}} probed {port_data} and detected a {protocol} web service",
107+
)

bbot/test/test_step_2/module_tests/test_module_fingerprintx.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import json
2+
13
from .base import ModuleTestBase
24

35

@@ -12,3 +14,55 @@ def check(self, module_test, events):
1214
and event.data["protocol"] == "HTTP"
1315
for event in events
1416
), "HTTP protocol not detected"
17+
18+
19+
class TestFingerprintxURLs(ModuleTestBase):
20+
"""Mocks fingerprintx output to verify URL_UNVERIFIED construction across IPv4/IPv6 and default/non-default ports."""
21+
22+
module_name = "fingerprintx"
23+
targets = [
24+
"127.0.0.1:80",
25+
"127.0.0.1:8443",
26+
"[::1]:443",
27+
"[::1]:8080",
28+
]
29+
config_overrides = {"modules": {"fingerprintx": {"skip_common_web": False}}}
30+
31+
# (host, port, protocol) -> what fingerprintx pretends to find
32+
fake_results = [
33+
("127.0.0.1", 80, "HTTP"),
34+
("127.0.0.1", 8443, "HTTPS"),
35+
("::1", 443, "HTTPS"),
36+
("::1", 8080, "HTTP"),
37+
]
38+
39+
async def setup_after_prep(self, module_test):
40+
results = self.fake_results
41+
42+
async def fake_run_process_live(self, command, **kwargs):
43+
for host, port, protocol in results:
44+
yield json.dumps({"ip": host, "host": host, "port": port, "protocol": protocol})
45+
46+
module_test.monkeypatch.setattr(module_test.module.__class__, "run_process_live", fake_run_process_live)
47+
48+
def check(self, module_test, events):
49+
urls = {e.data["url"] for e in events if e.type == "URL_UNVERIFIED"}
50+
expected = {
51+
"http://127.0.0.1/",
52+
"https://127.0.0.1:8443/",
53+
"https://[::1]/",
54+
"http://[::1]:8080/",
55+
}
56+
assert expected.issubset(urls), f"missing URLs; got {urls}"
57+
58+
protocol_events = [e for e in events if e.type == "PROTOCOL"]
59+
assert any(
60+
e.host == module_test.scan.helpers.make_ip_type("::1") and e.port == 443 and e.data["protocol"] == "HTTPS"
61+
for e in protocol_events
62+
), "IPv6 PROTOCOL event missing — parent lookup likely broken"
63+
assert any(
64+
e.host == module_test.scan.helpers.make_ip_type("127.0.0.1")
65+
and e.port == 8443
66+
and e.data["protocol"] == "HTTPS"
67+
for e in protocol_events
68+
), "IPv4 PROTOCOL event missing"

0 commit comments

Comments
 (0)