Cero: Mining Domain Names from TLS Certificates at Scale
Hook
Every HTTPS handshake leaks information—not through a vulnerability, but by design. SSL certificates broadcast lists of valid domain names before you even request a page, and Cero turns this feature into a reconnaissance superpower.
Context
Traditional subdomain enumeration relies on DNS brute-forcing, certificate transparency logs, or passive DNS databases. Each approach has blind spots: brute-forcing is slow and incomplete, CT logs only capture publicly logged certificates, and passive DNS misses internal infrastructure. But there's another vector hiding in plain sight—the certificates themselves.
When a server initiates a TLS handshake, it presents a certificate containing Subject Alternative Names (SANs) and Common Names that list every domain the certificate covers. A single certificate might reveal dozens of subdomains, internal hostnames, or related domains in one connection. This makes certificate scraping particularly valuable during the reconnaissance phase of security assessments, bug bounty programs, or infrastructure auditing. Glebarez's Cero was built specifically for this use case: connect to arbitrary hosts, extract domain names from their certificates, and do it fast enough to scan entire network ranges. Written in Go, it leverages goroutines to achieve the kind of concurrency needed to make mass certificate scraping practical.
Technical Insight
Cero's architecture is deceptively simple, which is exactly why it works well. At its core, the tool establishes TLS connections using Go's crypto/tls package, captures the certificate during handshake, and parses the SANs and CN fields for domain names. The power comes from wrapping this operation in goroutine-based concurrency and providing flexible input handling.
The concurrency model uses a semaphore pattern implemented with a buffered channel to limit simultaneous connections. When you specify -c 100, Cero creates a channel with 100 slots, and each goroutine must acquire a slot before attempting a connection. This prevents overwhelming your network stack or triggering connection limits while still maintaining high throughput. Here's how you'd use it to scan a CIDR range:
# Scan a /24 network on HTTPS port with 200 concurrent connections
echo "192.168.1.0/24:443" | cero -c 200
# Scan multiple ports on a single host
echo "example.com:443,8443,9443" | cero
# Combine with other tools for pipeline processing
cat targets.txt | cero -c 500 | sort -u > discovered_domains.txt
The tool's flexibility in target specification is noteworthy. It accepts domain names, IPv4/IPv6 addresses, and CIDR notation, with optional port lists. Input like 10.0.0.0/16:443,8443 expands to every IP in that range checked on both ports. This is particularly useful when you've identified an organization's netblock and want to discover all domains their certificates reference.
Under the hood, Cero uses Go's tls.Dial() with a custom configuration that skips certificate verification (InsecureSkipVerify: true). This is intentional—you're not validating certificate trust chains, you're extracting domain information regardless of validity. The timeout handling is worth examining:
// Simplified example of Cero's connection approach
func extractDomains(host string, port string, timeout time.Duration) ([]string, error) {
conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, port), timeout)
if err != nil {
return nil, err
}
defer conn.Close()
tlsConn := tls.Client(conn, &tls.Config{
InsecureSkipVerify: true,
})
if err := tlsConn.Handshake(); err != nil {
return nil, err
}
var domains []string
certs := tlsConn.ConnectionState().PeerCertificates
for _, cert := range certs {
// Extract CN
if cert.Subject.CommonName != "" {
domains = append(domains, cert.Subject.CommonName)
}
// Extract SANs
domains = append(domains, cert.DNSNames...)
}
return domains, nil
}
This pattern—separate TCP connection with timeout, then TLS handshake—gives you control over both connection establishment and TLS negotiation timeouts. In practice, Cero defaults to 4 seconds total, which balances speed against catching slow-responding hosts.
The output handling demonstrates thoughtful UX design for a CLI tool. Default mode prints only discovered domains (one per line), perfect for piping to other tools. Verbose mode (-v) adds error reporting, showing which hosts failed and why. This dual-mode approach respects the Unix philosophy: default behavior for composition, verbose for debugging.
One clever detail is the protocol flexibility. While the tool is commonly used for HTTPS (port 443), it works with any TLS-wrapped protocol: SMTPS (465), IMAPS (993), FTPS (990), or custom applications. If a service speaks TLS, Cero can extract domains from its certificate. This makes it useful beyond web reconnaissance—you can map mail server infrastructure, discover internal service names, or audit certificate deployment across different protocols.
Gotcha
Cero's focused design means it deliberately lacks features that might seem obvious. The most significant limitation is SNI (Server Name Indication) handling. The tool doesn't let you specify custom SNI values, which matters when scanning IP addresses that host multiple virtual domains. Many servers present different certificates based on the SNI value in the ClientHello message. Without SNI customization, you'll only see the default certificate for an IP, potentially missing dozens of other domains hosted on the same server. This is particularly problematic with CDNs or shared hosting environments where a single IP might serve hundreds of distinct domains.
The error handling philosophy also creates practical challenges. In default mode, Cero silently suppresses errors—a connection timeout, TLS handshake failure, or certificate parsing error produces no output. This is great for clean pipelines but terrible for troubleshooting. You might scan a network range, see fewer results than expected, and have no idea whether hosts were offline, filtering your traffic, or using client certificate authentication. Enabling verbose mode helps, but then you're manually filtering stderr from the domain output. There's no middle ground like error counting or summary statistics.
Rate limiting is completely absent. The concurrency flag controls simultaneous connections, but there's no built-in way to spread requests over time. Scanning at -c 1000 will hammer the target network with connection attempts as fast as your bandwidth allows. Against IDS/IPS systems or rate-limited infrastructure, this approach is likely to trigger alarms or get your IP blocked. You'll need external rate limiting via tools like pv or custom scripting if stealth matters.
Verdict
Use Cero if you're performing reconnaissance on known network ranges and need fast domain discovery across multiple hosts and ports, especially when working with pipelines or automation where clean output matters. It excels in scenarios where you have IP addresses or CIDR blocks and want to map the certificate-visible attack surface quickly. The concurrency model makes it practical to scan /16 or larger networks in reasonable timeframes, and the protocol flexibility means you can audit non-HTTP TLS services that other tools ignore. Skip it if you need SNI manipulation to enumerate virtual hosts on shared IPs, require detailed certificate analysis beyond domain extraction, or need built-in rate limiting for stealthy reconnaissance. For those cases, consider combining masscan for discovery with openssl s_client for detailed examination, or use dedicated subdomain tools like subfinder that incorporate certificate transparency logs alongside other sources.