Mining Certificate Transparency Logs for Bug Bounty Gold
Hook
Every time a company spins up a new subdomain and requests an SSL certificate, it broadcasts that information to the entire internet. Bug bounty hunters who know where to listen get first access to unexplored attack surface.
Context
Traditional subdomain enumeration is noisy and obvious. Tools like subfinder and amass query DNS records, scrape search engines, and hit third-party APIs—all activities that leave traces and provide only point-in-time snapshots. If a company deploys new infrastructure tomorrow, you won't know unless you run your scans again.
Certificate Transparency (CT) logs changed the game. Mandated by browser vendors to combat fraudulent certificates, CT logs are append-only public ledgers where Certificate Authorities must publish every SSL/TLS certificate they issue. This creates an unintentional goldmine for reconnaissance: a live feed of every new HTTPS endpoint coming online, searchable and free. The nashcontrol/bounty-monitor tool weaponizes this data source specifically for bug bounty hunters, monitoring CT logs continuously for new certificates matching domains in popular bounty programs.
Technical Insight
Bounty Monitor's architecture is deceptively simple but reveals sophisticated design choices around data persistence and signal-to-noise optimization. At its core, the tool subscribes to CT log feeds (typically via CertStream or direct log monitoring), filters incoming certificates against a curated list of bug bounty domains, and maintains state in a local SQLite database to avoid reporting duplicates.
The filtering logic operates on certificate Subject Alternative Names (SANs), where wildcard certificates and multi-domain certs can contain dozens of subdomains in a single entry. Here's the conceptual flow:
# Simplified example of CT log processing
import sqlite3
import certstream
import dns.resolver
def process_certificate(message, context):
if message['message_type'] == "certificate_update":
all_domains = message['data']['leaf_cert']['all_domains']
for domain in all_domains:
# Check against bounty program list
if matches_bounty_scope(domain):
# Age filtering - only certificates from last 90 days
cert_age = calculate_cert_age(message['data']['leaf_cert'])
if cert_age <= 90:
# Deduplication via SQLite
if not in_database(domain):
# Liveness check
if is_domain_live(domain):
log_finding(domain)
store_in_db(domain)
def is_domain_live(domain):
try:
answers = dns.resolver.resolve(domain, 'A')
return len(answers) > 0
except:
return False
The 90-day age filter is brilliant for bug bounty context. Certificates older than three months represent established infrastructure that other researchers have likely already tested. New certificates signal fresh deployments—staging environments, new microservices, forgotten admin panels—that represent unexplored attack surface. This temporal filtering dramatically reduces noise.
The SQLite persistence layer solves a critical problem: CT logs are infinite streams. Without state management, you'd see the same subdomain every time you restart the tool. The database schema is minimal—typically just a domains table with columns for subdomain, discovery timestamp, and liveness status. This allows the tool to run continuously as a background service, only alerting on genuinely new findings.
The liveness checking is where things get interesting. Not every certificate in CT logs corresponds to a resolvable domain. Companies request certificates for planned infrastructure that never launches, for domains they let expire, or for typo variations they're defensively registering. Bounty Monitor performs DNS resolution checks before reporting findings, filtering out roughly 30-40% of certificates that lead nowhere. This active verification step contradicts the "passive" reconnaissance label but is essential for practical use.
The tool's data source—whether CertStream (a real-time CT log aggregator) or direct log polling—determines latency and coverage. CertStream is convenient but introduces a third-party dependency and potential delays. Direct log monitoring (using Google's CT log APIs, for example) offers more control but requires handling multiple log sources since CAs can submit to different logs. The repository's implementation details reveal these architectural tradeoffs:
# Example of CertStream subscription
certstream.listen_for_events(
callback=process_certificate,
url='wss://certstream.calidog.io/'
)
# vs. direct CT log polling
import requests
from cryptography import x509
def poll_ct_logs():
logs = get_all_ct_logs() # From log list JSON
for log in logs:
tree_size = get_tree_size(log['url'])
entries = get_entries(log['url'], start=last_seen, end=tree_size)
for entry in entries:
cert = x509.load_der_x509_certificate(entry['leaf_input'])
process_domains(cert.extensions)
The direct polling approach requires tracking tree size per log and handling pagination, but eliminates external dependencies and provides access to the full firehose of certificate data without third-party filtering.
Gotcha
The tool's Python 2.7 heritage is its Achilles' heel. The repository shows signs of abandonment—no commits addressing Python 3 migration, outdated dependency specifications, and compatibility with libraries that have since evolved or deprecated their APIs. Running this in production means accepting security risks from unmaintained dependencies, particularly concerning for a security-focused tool.
CT log coverage isn't universal. While major CAs like Let's Encrypt, DigiCert, and Cloudflare reliably publish to CT logs, internal CAs, self-signed certificates, and some regional CAs don't. If your bounty target uses internal PKI for development environments or relies on certificate providers with poor CT compliance, you'll miss those subdomains entirely. Additionally, CT logs have varying submission delays—anywhere from seconds to hours—meaning you're not truly getting "instant" notifications. For time-sensitive bounties where being first matters, this latency could mean another researcher discovers the subdomain through active scanning before your CT monitor alerts you. The tool also lacks any notification mechanism beyond log files, requiring you to either continuously tail logs or write your own integration layer for Slack, Discord, or email alerts—a surprising omission for a monitoring tool.
Verdict
Use if: You're actively hunting on bug bounty programs and want a set-and-forget background monitor for new infrastructure deployments, you're comfortable forking and modernizing Python 2.7 code (consider this a learning opportunity), or you want to understand CT log monitoring architecture before building your own version with modern tooling. Skip if: You need production-ready tooling with active maintenance and security updates, you require real-time alerting integrations (no native Slack/Discord/webhook support), you're doing one-off reconnaissance where tools like subfinder provide faster results, or your target domains use internal CAs that don't publish to CT logs. The core concept remains invaluable—passive subdomain discovery via CT logs is one of the best signal-to-noise reconnaissance techniques available—but the implementation needs modernization. Fork it, port it to Python 3, add notifications, and you'll have a genuinely useful addition to your bug bounty toolkit.