From 7dff8b41d57a9e2a396a4138a3900a1a6eb2d7dd Mon Sep 17 00:00:00 2001 From: Justin Bollinger Date: Wed, 29 Jul 2026 13:34:12 -0400 Subject: [PATCH] fix: scope UDS version probe to UDS features; clarify --brute-mac -H error The startup version probe hit the UDS port (8443) for every -H host, even for --brute-mac and plain config/phone scans that never touch UDS. Against a host where UDS is firewalled or not listening, those runs paid a full read timeout and printed a misleading "Could not retrieve CUCM version" error unrelated to what the user asked for. Gate the probe to the features that actually use UDS (--servers, --directory, --userenum, --spray). Separately, --brute-mac with -H and no seeded prefixes printed "You must specify at least one phone with -p (or a CUCM server with -H)", implying -H was missing when it was in fact supplied. --brute-mac never queries the server; it replays MAC prefixes already harvested by --userenum/--spray or a phone scan. The message now names the host and points at the seeding steps. Co-Authored-By: Claude Fable 5 --- src/seeyoucm_thief/thief.py | 22 +++++++++++--- tests/test_brute_host_prefixes.py | 49 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/seeyoucm_thief/thief.py b/src/seeyoucm_thief/thief.py index ff6d04a..0b05e66 100644 --- a/src/seeyoucm_thief/thief.py +++ b/src/seeyoucm_thief/thief.py @@ -2835,7 +2835,13 @@ def main(): ) quit(0) - if CUCM_host: + # The version probe only informs the UDS-based features; it queries the UDS + # port (8443) and has nothing to say for --brute-mac or plain config/phone + # scans. Running it unconditionally made those runs pay a full UDS read + # timeout and print a misleading "could not retrieve version" error against + # hosts where UDS is firewalled or not listening. + uds_feature = args.servers or args.directory or args.userenum or args.spray + if CUCM_host and uds_feature: version_info = get_version(CUCM_host, port=args.uds_port) if version_info: v = version_info.get('version', 'unknown') @@ -3014,9 +3020,17 @@ def main(): else: db_prefixes = all_prefixes if not db_prefixes: - print('You must specify at least one phone with -p (or a CUCM server with -H) when using --brute-mac') - if not no_db: - print(' (and no previously discovered phones were found in the database)') + if CUCM_host: + # -H was given, but --brute-mac does not itself query the + # server: it replays MAC prefixes already harvested into the + # database by --userenum/--spray or an earlier phone scan. + print(f'--brute-mac found no MAC prefixes for {CUCM_host} in the database.') + print(' Run --userenum or --spray against it first, scan a phone with -p,') + print(' or pass a phone IP directly with -p to seed prefixes.') + else: + print('You must specify at least one phone with -p (or a CUCM server with -H) when using --brute-mac') + if not no_db: + print(' (and no previously discovered phones were found in the database)') quit(1) prefix_len = 12 - brute_mac_len if prefix_len < 0: diff --git a/tests/test_brute_host_prefixes.py b/tests/test_brute_host_prefixes.py index a2cfe7e..c01ad3d 100644 --- a/tests/test_brute_host_prefixes.py +++ b/tests/test_brute_host_prefixes.py @@ -1,5 +1,7 @@ import sqlite3 +import pytest + import thief @@ -48,3 +50,50 @@ def test_brute_mac_accepts_host_without_phone(tmp_path): ) assert 'You must specify at least one phone' not in result.stdout assert 'MAC brute force mode enabled using 1 MAC prefix' in result.stdout + + +def test_brute_mac_does_not_probe_uds_version(monkeypatch, tmp_path, capsys): + """--brute-mac must not run the UDS version probe: it never touches UDS, so + a firewalled 8443 would otherwise cost a full read timeout and a misleading + 'could not retrieve version' error.""" + calls = [] + monkeypatch.setattr(thief, 'get_version', lambda *a, **kw: calls.append(kw) or None) + db_file = str(tmp_path / 'thief.db') + thief.init_database(db_file) + monkeypatch.setattr('sys.argv', + ['thief', '-b', '1', '-H', 'cucm1', '--db', db_file]) + with pytest.raises(SystemExit): + thief.main() + out = capsys.readouterr().out + assert calls == [] + assert 'Could not retrieve CUCM version' not in out + + +def test_brute_mac_empty_db_message_names_host(monkeypatch, tmp_path, capsys): + """With -H but no seeded prefixes, the error should point at seeding steps, + not claim -H was missing.""" + monkeypatch.setattr(thief, 'get_version', lambda *a, **kw: None) + db_file = str(tmp_path / 'thief.db') + thief.init_database(db_file) + monkeypatch.setattr('sys.argv', + ['thief', '-b', '1', '-H', 'cucm-empty', '--db', db_file]) + with pytest.raises(SystemExit): + thief.main() + out = capsys.readouterr().out + assert 'no MAC prefixes for cucm-empty' in out + assert 'You must specify at least one phone' not in out + + +def test_servers_feature_still_probes_uds_version(monkeypatch, tmp_path): + """UDS features must keep running the version probe.""" + calls = [] + monkeypatch.setattr(thief, 'get_version', + lambda *a, **kw: calls.append(kw) or {'version': '14.0', 'prefix': None}) + monkeypatch.setattr(thief, 'get_servers_api', lambda *a, **kw: []) + db_file = str(tmp_path / 'thief.db') + thief.init_database(db_file) + monkeypatch.setattr('sys.argv', + ['thief', '--servers', '-H', 'cucm1', '--db', db_file]) + with pytest.raises(SystemExit): + thief.main() + assert len(calls) == 1