Open Season: Certificate Transparency
Part 1 of the Open Season series from the THOR Collective. All free tools, no vendor gatekeeping.
Most data sources catch an adversary after they act. Certificate Transparency catches them before.
When an operator stands up new infrastructure, say a phishing page or a C2 redirector, they almost always want TLS on it. A padlock makes a fake login look real, and browsers punish plain HTTP, so they request a certificate. The moment a publicly trusted CA issues it, that certificate is written into the Certificate Transparency logs, public append-only ledgers built so anyone can audit what CAs hand out.
The operator never chose to publish the domain. The CA did it for them, automatically, often before the service is even reachable. That gap is what you hunt in: the window between cert issued and attack launched, sometimes hours, sometimes days.
And it is completely free. CT was built as a public good and it remains one, no matter how many vendors wrap it in a dashboard and charge for the view.
What the data tells you
A CT log entry is a certificate plus metadata. For hunting, the fields that matter are the common name and the Subject Alternative Names (the domains the cert is valid for), the issuer (which CA signed it), the validity window (not_before and not_after), and the log entry timestamp, which is when the certificate actually reached the log.
The single most useful field is the SAN list. A certificate is frequently valid for more than one name, and operators bundle related infrastructure onto one cert all the time: the phishing domain, its www variant, a staging subdomain, sometimes a second unrelated campaign domain they were too lazy to separate. One cert, read carefully, hands you the operator’s other names for free.
The second useful field is the log entry timestamp. Certificates hitting the logs in a tight cluster for similar-looking names are a batch of infrastructure going up at once, and that rhythm is something you can cluster on. Use that one rather than not_before, for reasons in the traps below.
As of early 2026 there are roughly thirty to forty active CT log shards, operated by Google, Cloudflare, Let’s Encrypt, Sectigo, and a few others. Chrome and Apple each maintain their own list of trusted logs, and the two do not overlap perfectly. Chrome’s is the one most tooling tracks. To be trusted in a modern browser a cert needs proof that it was logged, and that proof is a Signed Certificate Timestamp, or SCT, a log’s signed promise that it accepted the certificate. You do not have to talk to the logs directly. Aggregators do that for you.
Why adversaries leave traces in it
Because the alternative is worse for them. Skip CT and the cert is untrusted, which means browser warnings, which means victims bounce before they type their password. The whole point of the fake login page is that it looks legitimate, and looking legitimate now requires a logged certificate. CT compliance is load-bearing for the attack.
They can use self-signed certs on non-browser infrastructure like C2, and some do. But anything a human opens in a browser needs a real cert, and a real cert gets logged. The economics of phishing leave no way around it.
Know what this lens cannot see, though. CT only covers publicly trusted CAs, so a private CA leaves no entry, and neither does IP-only infrastructure that never gets a name. Anything behind a provider that terminates TLS on a shared certificate (Cloudflare universal certs, Vercel, most phishing-as-a-service platforms) may never produce an entry you can tie to the operator. Absence from CT is not absence of infrastructure.
The behaviors that show up:
Batch issuance.
An actor registering and certing ten lookalike domains in an afternoon produces ten certs that hit the logs within minutes of each other. Cluster on the timing.
Naming conventions.
Operators reuse patterns. Once you see login-acme-support.com, you go looking for login-acme-secure.com and acme-support-login.com, because the same person tends to name things the same way.
CA preference.
Throwaway phishing infrastructure leans on free, automated CAs because cost and speed matter at volume. That is a weak signal on its own, but it stacks with the others.
How to query and pivot
The free workhorse is crt.sh, Sectigo’s CT search engine. It indexes the major logs into a Postgres database and exposes both a web UI and a JSON API. Start simple:
# every name ever seen in a cert under a domain
curl -s 'https://crt.sh/?q=%25.example.com&output=json' \
| jq -r '.[].name_value' | sed 's/^\*\.//' | sort -u
The % is a SQL wildcard, so %.example.com pulls every subdomain that has ever appeared in a logged certificate. Write it as %25 in the URL, which is how % is percent-encoded. The bare version usually works but is not valid encoding, and it will fail behind some proxies. This alone surfaces dev, staging, and internal-looking hosts that were never meant to be public. For an adversary domain, it surfaces their other names.
Point it at your own domain while you are here. Every name in a publicly trusted cert is public the moment the CA issues it, so an internal hostname in one is a hostname you have published. If it should not be, it belongs on a private CA or behind a wildcard.
When you need real power, crt.sh exposes its Postgres directly, no account, read-only guest access:
psql -h crt.sh -p 5432 -U guest certwatch
From there you can run actual SQL against the certificate data, which is how you do timing clustering and pattern hunting that the web UI cannot:
-- one row per certificate, ordered by when it actually hit the logs
SELECT min(le.entry_timestamp) AS logged,
x509_notBefore(c.certificate) AS not_before,
x509_issuerName(c.certificate) AS ca
FROM certificate c
JOIN ct_log_entry le ON le.certificate_id = c.id
WHERE to_tsquery('certwatch', 'login-acme-support.com') @@ identities(c.certificate)
GROUP BY c.id, c.certificate
ORDER BY logged;
Grouping on c.id collapses a cert logged to several logs into one row, and min(entry_timestamp) gives you the moment it first appeared. Ordering on that rather than not_before is deliberate, for reasons in the traps below. The to_tsquery match is the current way in: as of July 2026 crt.sh has retired the old certificate_identity table for a full-text index, so queries against it now error, and the tsquery goes on the left.
That index does prefix and suffix matching, not substring, so you cannot ask crt.sh for every name containing a string. You have to arrive with names. The public Postgres is slow and kills anything running over about a minute, so be specific.
The pivots, in the order they tend to pay off:
SAN expansion.
Pull a suspicious cert. Read every name on it. Each new name is a new lead, and you feed each one back into crt.sh to find its certs and its SANs in turn. This is the core loop.
Timing clustering.
Sort candidate certs by log entry time. Names that cluster within minutes or hours of each other are very likely the same operator’s batch. The tighter the cluster and the more the names rhyme, the stronger the signal.
Naming-convention sweeps.
Take the pattern from one confirmed domain and go looking for its siblings. Operators are creatures of habit, and the database remembers every cert. crt.sh will not do this as a free-text substring search, so generate the candidate names yourself and check them in batches. A few hundred generated names cost nothing but patience. The indexes that do offer substring matching over CT gate it behind a paid tier or a browser session, so treat those as a manual spot-check.
Mind the traps though.
Wildcard certs (*.example.com) are candidates, not confirmed hosts, so always resolve before you trust them. Every certificate appears in the logs at least twice, once as a precertificate and once as the final cert, so deduplicate before you count.
And do not build timing analysis on not_before. The issuing CA sets it and every CA does it differently: Let’s Encrypt backdates about an hour, some a full day, some pin to midnight UTC. Compare certs from two CAs on that field and you are reading two clocks with two offsets, which invents bursts that never happened and flattens ones that did. Use the log entry timestamp. not_before is a CA’s opinion; the log entry is a fact.
For watching issuance live, certstream gives you a real-time firehose of every cert hitting the logs:
# wss://certstream.calidog.io (filter for your patterns in real time)
pip install certstream
Point a small script at the stream, match on your target’s naming patterns or brand strings, and you get alerted within minutes of a new lookalike cert being minted. For a handful of domains without writing code, Certspotter from SSLMate offers free monitoring for up to five domains with email alerts.
One warning before you build on it. The public certstream.calidog.io endpoint fails in the worst possible way: the socket opens, stays up, and no certificates arrive. Nothing errors, so your watcher looks healthy and is deaf. Tested while writing this with the official client and with raw websockets, both endpoints, zero certificates.
The website will not tell you either. On connect the page replays a cached /latest.json into the live pane at a randomized, human-looking pace, and that cache has not moved since October 2025, so a dead feed still scrolls convincingly. Treat the public firehose as a demo. If the alert matters, self-host certstream-server-go, or certstream-server-rust at certstream.dev if you want the stream over SSE instead. And alert on silence as well as on matches, because here silence is ambiguous.
One note if you build your own tooling. CT is migrating from the original RFC 6962 dynamic logs to the tile-based Static CT API (Sunlight), and Chrome’s trusted list already mixes both. Query through crt.sh, certstream, or Certspotter and this is handled for you. Write your own log reader and you need to speak both during the changeover.
In practice: Ghost Stadium
The question going in: can certificate data surface campaign infrastructure the published sets have missed, and can it at least date the operation?
Point the CT loop at Ghost Stadium and the operator turns out to live by the same economics as everyone else. The kit is a fake FIFA World Cup 2026 ticket storefront meant to be opened in a browser, so every live node needs a trusted cert, and every cert lands in the logs. Pull the certs for fifa-sg[.]shop, a confirmed net-new node that has since been deleted, and the issuance pattern is louder than any SAN. On 2026-05-17 it drew four certificates in twenty-one minutes from three different CAs, Google Trust Services, then Let’s Encrypt, then SSL Corporation, back to back. That is automated standup, an operator spraying certificate requests the moment the domain goes live. Measure the same burst on not_before and it stretches to seventy minutes, because the three CAs backdate by different amounts. The tight number is the real one.
shop-26fifa[.]com shows the same shape three days later, four certs across two CAs inside fourteen minutes on 2026-05-20. The domain was registered at 18:30:41 UTC and its first certificate hit the logs under three minutes later. Cert issuance and registration are not just the same day, they are the same event, which is why this lens and Part 4 corroborate each other. It is also the cleanest argument for reading log entries: that first certificate carries a not_before of 17:34:45, fifty-six minutes before the domain it covers existed.
Check one thing before you call that automation. The registrar was NameSilo and the certificates came from Let’s Encrypt and SSL Corporation, unrelated vendors, so somebody’s own tooling drove it. fifa[.]house is the counterexample: registered through GoDaddy, certified by GoDaddy thirty-seven seconds later. That interval says something about GoDaddy’s provisioning and nothing at all about the operator. When registrar and CA are the same company, a fast cert is a checkout bundle, not tradecraft.
The reissuance rhythm then just keeps going: fifa[.]house cycled through twenty-one certs from January to April across GoDaddy, Let’s Encrypt, and TrustAsia, tempo you read straight off the log entries.
SAN co-tenancy is where this gets interesting, because the same campaign does it both ways. fifa-sg[.]shop and shop-26fifa[.]com are disciplined: apex, wildcard, nothing else. Read those two and you would conclude the operator certs one name at a time.
fifa[.]house is the opposite. One of its certificates carries forty-five names spanning about twenty registrable domains: fifa[.]bio, fifa[.]cafe, fifa[.]college, fifa[.]tax, the whole fifa-com[.]* run across seven TLDs, then www-fifaworldcup[.]* across seven more. Feed three seed domains into the SAN loop and twenty-five come back out.
Widen that to a sample of six hundred domains from the published set and the split holds: ninety-one percent of their certificates cover a single registrable domain, nine percent bundle more, and a hundred and sixty-two certs carry exactly one hundred names, which is Let's Encrypt's SAN limit. Somebody is filling certificates to the limit and somebody else is provisioning one at a time. The certificates are where that seam shows.
Expanding on shared certs drags in infrastructure that is merely adjacent. One of those hundred-name certs mixes FIFA lookalikes with Chinese gambling brands that may be a different operation entirely, and six more turned out to be a bulk-cert service carrying unrelated tenants that have nothing to do with this campaign at all. The highest-yield pivot here is also the one most likely to hand you somebody else's estate. Confirm before you count.
Ghost Stadium is also heavily reported, and that shapes what CT is good for. Group-IB mapped more than 4,300 fraudulent FIFA domains registered since August 2025, but that total spans six fraud schemes and four independent threat actors; Ghost Stadium itself is the 300-plus domains actively serving the kit. Group-IB describes the operator as Chinese-speaking and financially motivated, and that is as far as attribution goes here. Validin went further on infrastructure and published a suspected set of 6,113 domains, about half still resolving, with the full list on GitHub where anyone can diff against it.
So check rather than assert. The SAN loop turned three seed domains into twenty-five registrable domains, forty-nine DNS names once you count www variants. Diff those against Validin’s set and forty-six of the forty-nine were already on it. Two of the three misses were seeds rather than discoveries — fifa-sg[.]shop and shop-26fifa[.]com went into the loop, they did not come out of it — and the third is a www variant of a listed domain. SAN expansion produced exactly one name nobody had.
Read that before you take it as a verdict. Most of Validin’s set came from a lookalike regex that matches any fifa-shaped domain, so fifa[.]cafe was going to be on that list whether or not a human ever looked at it. Against a regex superset, “already published” means the name matched a pattern. Against Group-IB’s 300 active domains it would mean someone investigated it. Those are different bars, and CT grades worse against the first one than it deserves.
Corroboration with timestamps is still worth having when you are deciding whether to act on someone else’s list. But the net-new nodes in this series came from pairing the cert view with a kit-specific fingerprint in a later installment, not from certificates alone. One more thing from Validin that matters for what comes next: every active domain in their set sits behind Cloudflare, which is exactly the wall Part 2 has to get over.
Then take one confirmed node and resolve it. These answered from 104.21, 172.64, and 172.67 space while they were live, all Cloudflare, AS13335. CT told you the name exists and the minute its cert was minted. It cannot tell you where the box actually sits, because the operator parked Cloudflare in front of it. That gap is exactly what the next source exists to close.
Assessment, high confidence: against an actor this heavily reported, Certificate Transparency expands a name list fast but rarely past what someone has already published. Use it to confirm cluster membership, read the batch rhythm, and date the standup. Expect a thin net-new edge, not a new cluster.
Hunt hypothesis template
HYPOTHESIS
An adversary targeting our brand stages lookalike infrastructure
and certs it before launch. New certificates matching our brand
patterns appear in CT logs ahead of the attack and cluster by
issuance time and naming convention.
DATA SOURCES
Primary: crt.sh (web, JSON API, direct Postgres)
Live: certstream (wss://certstream.calidog.io), Certspotter
Backup: Censys/Shodan cert search (free), MerkleMap, Merkle Town
Cross-ref: passive DNS (Part 2), scan data (Part 3),
registration data (Part 4)
PIVOT STEPS
1. Start from known domains and pull their certs. crt.sh
matches names, not substrings.
2. For each hit, expand the SAN list. Every name is a new lead.
3. Sort candidates by log entry time, not not_before. Flag
tight issuance clusters.
4. Generate names from the convention and check them in
batches. crt.sh has no substring search.
5. Resolve survivors; confirm via pDNS co-residence and scan-time
cert match before promoting to confirmed.
6. Add matched patterns to a certstream watch for live alerting.
EXPECTED SIGNAL
A cluster of lookalike domains sharing a naming convention, with
certs hitting the logs in a tight window, from one CA or
sprayed across several, resolving to shared or adjacent hosting.
VALIDATION / TRIAGE
Deduplicate precert/leaf pairs. Treat wildcards as candidates,
not hosts. Resolve before trusting. Require a second independent
link (shared IP, shared registrant) before confirming.
ATT&CK MAPPING
T1583.001 Acquire Infrastructure: Domains
T1588.004 Obtain Capabilities: Digital Certificates
T1587.003 Develop Capabilities: Digital Certificates
T1608.003 Stage Capabilities: Install Digital Certificate
T1596.003 Search Open Technical Databases: Digital Certificates
Tooling quick reference
FREE, NO ACCOUNT (start here)
crt.sh crt.sh/?q=%25.domain&output=json
Web UI, JSON API, and direct Postgres:
psql -h crt.sh -p 5432 -U guest certwatch
certstream wss://certstream.calidog.io
Real-time firehose of all new certs.
Public server accepts the connection then
delivers nothing, which looks like a quiet
day. Self-host certstream-server-go for
anything load-bearing.
FREE MONITORING / BACKUP INDEXES
Certspotter api.certspotter.com/v1/issuances
Free alerts for up to 5 domains. Works
without an account for lookups.
Merkle Town Cloudflare CT dashboard/search
MerkleMap merklemap.com (alternate CT search)
Censys / Shodan cert search. Substring search exists here
but free tiers restrict it to the browser;
the search APIs want a paid plan.
PASSIVE SUBDOMAIN TOOLS THAT USE CT
subfinder, amass use CT logs as a primary passive source
OPTIONAL COMMERCIAL UPGRADES (not required)
Censys/Shodan paid cert data, SSLMate paid monitoring,
vendor CT feeds. None needed for anything above.
Two things are true about the hunt above. The SAN loop worked exactly as advertised and turned three domains into twenty-five. And all but one were already on a list somebody published months ago. CT did not fail there. It walked ground that had already been mapped, and told us when each stone was laid.
That is the shape of this source. What you get out of CT scales with how early you are and how few people have looked before you. Arrive after the reporting lands and it corroborates and dates. Arrive first and it is the only place the infrastructure exists yet, because the certificate is minted before the site serves a page, before the mail goes out, before there is anything else to detect.
Which is the argument for pointing it at yourself. Nobody has published the list of domains that will impersonate your brand next quarter. On that hunt you are not late, you are first, and you already know the brand strings worth watching. The limitation that blunts CT against a well-reported actor does not apply when the target is you.
CT is the rare source where you are not chasing the adversary, you are waiting at the door they have to walk through. They need a trusted cert, that cert gets logged the moment a CA issues it, and the log is public and free for anyone to read. No vendor owns that.
Catch the cert before the campaign starts, and you are hunting the attack while it is still being built.
Up Next
Take fifa[.]house with you. CT handed us twenty-one certificates on it, a first-to-last window from January to April, and roughly twenty sibling domains riding its SANs. What CT cannot tell us is where any of it was hosted. The name is NXDOMAIN today, so there is nothing left to resolve, and while it was alive it answered from Cloudflare, which means even a live lookup at the time would have returned a proxy rather than the box. Same for fifa-sg[.]shop, dead since May. We have precise names and precise minutes, and no addresses.
Open Season: Passive DNS. That gap is what the next source closes. Passive DNS is the historical record of what resolved where and when, the one place a dead name still has an address, and the way behind a CDN to an origin the operator forgot to firewall. It reverses an IP into every domain that shared it, and clusters campaigns by the hosting and nameservers they reuse. We anchor it on community-run data, no vendor gates.
Open Season is a recurring series from the THOR Collective exploring how practitioners can use open-source data to hunt adversary infrastructure. Each installment covers a single data source, soup to nuts. Want to contribute an installment or suggest a data source? Reach out.




