Passive subdomain enumeration via Certificate Transparency logs (crt.sh + optional Censys).
- Python 3.11+
- aiohttp
pip install aiohttppython main.py example.comOutput:
[*] Target : example.com
[*] Providers: CrtShProvider, CensysProvider
[*] Timeout : 30.0s
[*] Querying Certificate Transparency logs...
[+] Done - 5 unique subdomains found
TARGET example.com FOUND 5
▸ dev.example.com
▸ m.example.com
▸ products.example.com
▸ support.example.com
▸ www.example.com
python main.py example.com --json{
"domain": "example.com",
"count": 5,
"subdomains": [
"api.example.com",
"cdn.example.com",
"mail.example.com",
"staging.example.com",
"www.example.com"
]
}python main.py example.com --output results.txt
python main.py example.com --json --output results.jsonpython main.py example.com --timeout 60 # custom timeout (default: 30s)
python main.py example.com -v # verbose/debug logging| Flag | Type | Default | Description |
|---|---|---|---|
domain |
positional | Target domain, e.g. example.com |
|
--json |
flag | off | Output as JSON |
--output FILE |
string | Write output to file | |
--timeout SECS |
float | 30.0 |
HTTP request timeout in seconds |
--verbose / -v |
flag | off | Enable debug logging |
Censys is optional and requires a free API account from censys.io.
export CENSYS_API_ID="your-api-id"
export CENSYS_API_SECRET="your-api-secret"When credentials are present, Censys is queried automatically alongside crt.sh. If not set, it is silently skipped.
- Queries
crt.shfor all certificates matching%.example.com - Optionally queries Censys (if credentials are set)
- Parses
name_valueandcommon_namefields from each certificate entry - Normalizes names: lowercased, wildcard prefixes (
*.) stripped - Deduplicates, filters to valid subdomains of the target, returns sorted
Failed requests and rate limits are retried with exponential backoff (up to 4 attempts, capped at 60s).
python main.py tesla.com
python main.py github.com --json --output github_subs.json
python main.py google.com --timeout 120 -v
python main.py example.com --json | jq '.subdomains[]'Implement the CTProvider abstract base class in ct_sources.py and register it in get_providers():
from ct_sources import CTProvider
class MyProvider(CTProvider):
async def fetch(self, domain: str, session: aiohttp.ClientSession) -> list[dict]:
...
def get_providers() -> list[CTProvider]:
return [CrtShProvider(), CensysProvider(), MyProvider()]- Results reflect historical certificate data, not live/active subdomains
- Wildcard certs (
*.example.com) are excluded; they don't map to a specific subdomain - No DNS resolution is performed; this tool is entirely passive
