SeeYouCM Thief
June 12, 2026 · View on GitHub
Multi-threaded tool to automatically download and parse configuration files from Cisco phone systems searching for SSH credentials. Features intelligent caching, automatic backoff protection, and MAC address brute forcing capabilities.
Features
- Multi-threaded downloads: 40 parallel worker threads for fast credential extraction
- Intelligent caching: SQLite database prevents redundant TFTP requests
- Automatic protocol fallback: TFTP by default with automatic HTTP fallback
- TFTP server protection: Automatic backoff manager prevents server overload
- MAC address brute forcing: Try 4,096 MAC variations (3 hex characters) per detected phone
- Gowitness integration: Load phone targets directly from gowitness database
- CSV export: Export discovered credentials to CSV format
- User enumeration: Extract usernames via CUCM User Data Services (UDS) API
- Password spray: HTTP Basic Auth spray against the UDS user endpoint with persistent per-username rate limiting and a pre-flight oracle probe
- Credential verification: replay harvested credential pairs against the CUCM CCMAdmin portal, with a full audit trail of timestamped attempts
Usage
Basic Usage
Download configs from CUCM server (will attempt to get list of all phones):
./thief.py -H <CUCM Server IP>
Single Phone Target
Specify a phone IP address to detect CUCM and discover credentials:
./thief.py -p <Cisco Phone IP>
Multiple Phone Targets
Specify multiple phones (repeatable):
./thief.py -p 192.168.1.10 -p 192.168.1.11 -p 192.168.1.12
Gowitness Integration
Load phone targets from gowitness database:
./thief.py --gowitness /path/to/gowitness.sqlite3
MAC Address Brute Force
Brute force 4,096 MAC variations for each detected phone:
./thief.py -p <Phone IP> -b
./thief.py --gowitness <DB> -b -H <CUCM Server>
Subnet Enumeration
Enumerate and attack entire subnet:
./thief.py --subnet 192.168.1.0/24
User Enumeration
Extract usernames via CUCM UDS API:
./thief.py -H <CUCM Server> --userenum
--userenum also harvests the full corporate directory from /cucm-uds/users — names (including nickname), phone/home/mobile/pager numbers, email, directory URI, MS URI, department, title, manager, and the per-user UUID — and stores it in the database (uds_directory table). This is the same anonymously-readable data phones use for the Directory button, so it works without credentials. View it later with --show-db. The directory is always written to cucm_directory.csv (override with --directory-outfile).
Harvest the unauthenticated directory (--directory)
Pull the CUCM corporate directory from the unauthenticated UDS endpoint
/cucm-uds/users, without the device probing, config downloads, or password
spraying that --userenum performs. Every field the endpoint exposes is
captured: username, first/middle/last/nick/display name, extension
(phoneNumber), home/mobile/pager numbers, email, directory URI, MS URI,
department, title, manager, and the per-user UUID.
uv run thief --directory -H <cucm-host>
Results are always written to cucm_directory.csv (override with
--directory-outfile), printed as a console summary table, and stored in the
uds_directory table unless --no-db is set.
Note on DIDs: the unauthenticated UDS directory has no dedicated DID field.
The phoneNumber element is the directory number / extension, which in some
dial plans is itself the full DID. True external DIDs require authenticated AXL
access and are out of scope for this unauthenticated path.
Password Spray
Spray a single password across every UDS-enumerated user, with a default 1-hour-per-user rate limit:
./thief.py -H <CUCM Server> --spray --spray-password 'Summer2025!'
Iterate a password list, sleeping ~1 hour between rounds so each user is attempted at most once per hour:
./thief.py -H <CUCM Server> --spray -P passwords.txt
A pre-flight oracle probe sends one bogus credential to verify the endpoint validates auth. If the server returns 200 to a known-bad password, the run aborts before any real password is sent. Skip the probe with --no-spray-probe only after manually verifying the target. Every attempt is logged to the spray_attempts table; hits surface in --show-db.
Operator safety: If CUCM is configured with LDAP Authentication, end-user spray attempts pass through to AD. Confirm domain lockout policy before running and tighten --spray-rate-limit-hours if needed.
Verify Credentials
Take the credential pairs already harvested into the database and test them against the CUCM administration portal (CCMAdmin), recording every attempt for auditing:
./thief.py --verify # every stored pair vs every known host
./thief.py --verify -H <CUCM Server> # restrict to a single host
./thief.py --verify --verify-port 443 # CCMAdmin on 443 instead of the 8443 default
--verify reads every credentials row that has both a username and a password, dedupes to distinct pairs, and attempts a CCMAdmin form login (Tomcat j_security_check) for the cross-product of those pairs against every CUCM host known to the database (the union of harvested credential hosts, phone→CUCM mappings, and discovered cluster members). Pass -H/--host to narrow the host set to one target.
Every attempt — valid, invalid, or error — is written with a timestamp to the verification_attempts table for auditing. A (host, username, password) combination that already has a definitive result (valid/invalid) is skipped on later runs; error results (network failures, unexpected responses) are retried. Confirmed admin logins print live ([+] VALID admin: user@host) and surface in --show-db under "Verified Admin Credentials". Tune concurrency with --verify-threads (default: 10).
--verify requires the database (incompatible with --no-db) and is mutually exclusive with --brute-mac and --spray. As with the other features, plaintext passwords are stored in thief.db, which is created chmod 0600.
Operator safety: CCMAdmin login failures count against CUCM/AD account-lockout policy. Confirm the lockout threshold before running broad cross-product verification against production.
Database Operations
View cached results:
./thief.py --show-db
./thief.py --show-db -H <CUCM Server> # Filter by CUCM
Extract all cached configs to disk for offline review:
./thief.py --extract-configs ./configs
./thief.py --extract-configs ./configs --db custom.db
Files are written to ./configs/<cucm_host>/<filename>. Existing files are
preserved (re-runs are safe and incremental). Only successful downloads
with non-empty content are extracted.
Force re-download (bypass cache):
./thief.py -p <Phone IP> -b --force
Use custom database file:
./thief.py -p <Phone IP> --db custom.db
Disable database caching:
./thief.py -p <Phone IP> --no-db
Export Options
Export to CSV:
./thief.py -p <Phone IP> -b --csv results.csv
Command-Line Options
Target Specification
-H, --host: Specify CUCM server IP address-p, --phone: Specify Cisco phone IP (repeatable for multiple targets)--gowitness DB_FILE: Load phone targets from gowitness SQLite database-e, --enumsubnet: Enumerate and attack subnet in CIDR notation
Attack Options
-b, --brute-mac: Brute force MAC variations (4,096 combinations per phone). If no-pphones are given, reuses MAC prefixes discovered on a previous scan from the database (unless--no-db)--force: Bypass cache and force re-download of all configuration files--userenum: Extract usernames via CUCM User Data Services (UDS) API (paginates the full directory) and harvest the full directory records (names incl. nickname, phone/home/mobile/pager numbers, email, directory URI, MS URI, department, title, manager, UUID) into theuds_directorytable; always writescucm_directory.csv(override with--directory-outfile)--directory: Harvest the unauthenticated CUCM corporate directory from/cucm-uds/userswithout any device probing or config downloads — requires-H; always writescucm_directory.csv(override with--directory-outfile), prints a console table, and stores touds_directoryunless--no-db--directory-outfile FILENAME: Override the default CSV output path for--directoryand--userenum(default:cucm_directory.csv)--servers: Enumerate CUCM cluster members (hostnames + IPs) via UDS/cucm-uds/servers— requires-H--http: Use HTTP (port 6970) as the primary config download protocol with TFTP fallback (default: TFTP first, HTTP fallback)--uds-port PORT: Override the CUCM UDS API HTTPS port for--userenum,--directory, and--servers(default: 8443)--spray: Password-spray the UDS API (requires-H; mutually exclusive with--brute-mac)--spray-password PASSWORD: Single password to spray across all eligible users-P, --passwords FILE: Password list file; sprays each password in turn, sleeping ~1h between rounds--spray-threads N: Concurrent spray workers (default: 10)--spray-rate-limit-hours N: Per-username rate-limit window in hours (default: 1)--no-spray-probe: Skip the pre-flight oracle probe (use only after manual verification)--verify: Verify stored credential pairs against each known CUCM CCMAdmin portal (requires the database; mutually exclusive with--brute-macand--spray)--verify-port PORT: CCMAdmin HTTPS port for--verify(default: 8443; some clusters use 443)--verify-threads N: Concurrent verification workers (default: 10)
Output Options
--csv FILENAME: Export discovered credentials to CSV file--outfile FILENAME: Specify output file for enumerated usernames (default: cucm_users.txt)
Database Options
--db FILENAME: Specify SQLite database for caching results (default: thief.db)--no-db: Disable database caching and operate without persistent storage--show-db: Display summary of credentials stored in database and exit--extract-configs DIR: Extract all cached configuration files from the database intoDIR/<cucm_host>/<filename>and exit
Debugging
-d, --debug: Enable verbose output including all failed attempts and TFTP operations
How It Works
- Target Discovery: Detects phones via direct IP, gowitness database, or subnet enumeration
- CUCM Detection: Automatically identifies CUCM server from phone configuration
- MAC Detection: Extracts MAC addresses from phone hostnames (SEP format)
- Multi-threaded Brute Force: 40 workers simultaneously try TFTP/HTTP downloads
- Cache check: Skips previously attempted files
- Backoff protection: Automatically slows down if TFTP server shows errors
- Protocol fallback: Tries TFTP first, falls back to HTTP
- Credential Extraction: Parses XML configs for SSH credentials, passwords, usernames
- Database Storage: Caches all results to prevent redundant requests
Default TFTP File Enumeration
In addition to per-device SEP<MAC>.cnf.xml files, the tool always attempts to pull these well-known CUCM default files from each discovered TFTP server — no extra flag required:
XMLDefault.cnf.xmlSEPDefault.cnf.xmlSIPDefault.cnfITLFile.tlvCTLFile.tlvRingList.xmlRinglist-wb.xmlDistinctiveRingList.xmljabber-config.xml
These can surface firmware versions, trust-list presence, Jabber bootstrap configuration, and other recon-relevant metadata. Results are cached in the SQLite database and visible via --show-db. Credentials found in default files are keyed by filename (e.g. XMLDefault, ITLFile.tlv) rather than a device MAC address.
Setup
Using uv (recommended)
uv sync
uv run thief --help
Or install as a global CLI tool:
uv tool install .
thief --help
Using pip
pip install .
thief --help
Performance
- Multi-threading: 40 parallel workers process 4,096 MAC variations efficiently
- Intelligent caching: Database prevents re-downloading the same files
- Automatic throttling: Backoff manager protects TFTP server from overload
- Protocol optimization: TFTP (fast) with HTTP fallback (reliable)