OSINT and recon
OSINT Ethics and Privacy: What You Should Not Collect
Learn purpose, proportionality, consent and data minimisation for OSINT, with LGPD and GDPR basics and a one-page research plan template.
Learn how Certificate Transparency logs record public TLS certificates, and how to review crt.sh results for a domain you own without touching its hosts.
Certificate Transparency (CT) is a public record of TLS certificates. When a certificate authority (CA) issues a publicly trusted certificate, it writes that certificate to one or more CT logs. Anyone can read the logs, and nobody can quietly edit them. For a domain owner, this gives you a searchable history of public certificates issued for names under your domain, including certificates nobody on your team remembers requesting.
The protocol is specified in RFC 9162, "Certificate Transparency Version 2.0," published in December 2021. It replaces the original specification, RFC 6962. Check the status line before you cite it. RFC 9162 is an Experimental document, not an Internet Standards Track specification, and the RFC says so itself. It is still the reference for how logs work, and browsers rely on the same design.
CT does not encrypt anything, and it does not protect a server by itself. It is a visibility mechanism. It tells you which certificates were issued, when they were logged, and which names they cover. It does not tell you whether a host is online, what it serves, or who operates it today.
The first question most people ask is why a certificate must appear in a public log at all. The answer is browser enforcement, and the design includes a step that makes enforcement possible without a circular dependency.
The CT project explains the flow on its how-it-works page at https://certificate.transparency.dev/howctworks/. In simplified form:
The precertificate solves an ordering problem. A CA cannot get an SCT for the final certificate without first submitting something to a log, and a browser should not accept the final certificate before it has an SCT. The precertificate breaks that loop.
Logs are append-only. Entries can be added, but not deleted, modified, or inserted after the fact. Each log keeps a Merkle tree, which lets anyone verify that a given certificate is included (an inclusion proof) and that the log only grew over time (a consistency proof). Section 2 of RFC 9162 describes the tree. You do not need to implement it to use the data.
Two details matter for defenders. First, the guarantee is about logging, not about trust. A logged certificate may be valid, mistaken, or misissued, and the log cannot tell you which. Second, coverage is limited to certificates from publicly trusted CAs. Certificates from a private CA usually never reach a public log, so a clean CT result does not clear your whole estate.
Three uses come up again and again.
Inventory. Teams create hostnames for test environments, marketing pages, vendor integrations, and old projects. Many of those names never reach the DNS zone file that someone reviews. A certificate request leaves a record, so CT can surface names your inventory missed.
Spotting certificates nobody requested. If a certificate appears for your domain and nobody on your team asked for it, that deserves a look. It could be a vendor acting for you, an old account, a provider you forgot about, or an attacker who gained control of a validation path. The log alone cannot tell you which, so you escalate to the people who run the domain's DNS and certificate accounts.
Lifecycle hygiene. Expired certificates still appear in CT. That history is useful. You can see which names existed, and you can find retired services that still have DNS records.
A CT result is a lead, not a verdict. A name may be stale, belong to a vendor, be a wildcard that covers many hosts, or come from a typo. Each lead needs confirmation from the owner of the asset.
crt.sh is a public search front end for CT logs. A third party runs it. You can search in a browser, or get JSON by adding output=json to the query. Your query goes to that service, so search only for domains you are allowed to discuss. A domain name is public information. An unannounced product name or an internal hostname is not something to type into a third-party search box.
Other CT search services exist. Results can differ between indexes and between query times, so write down which service you used and when.
The lab only reads public CT data. You do not connect to any host you find. A DNS lookup of a name is a query to a resolver, not a connection to the host, but do not go further than that without the owner's permission.
Open https://crt.sh/?q=example.com in a browser. Replace example.com with your domain. Each row is one certificate. Read the issuer, the validity dates, and the names listed. Then try the wildcard form, https://crt.sh/?q=%.example.com, which asks for names below the domain. Compare the counts between the two searches.
Run:
curl -s 'https://crt.sh/?q=example.com&output=json' | jq -r '.[].name_value' | sort -u | head -50The pipeline works in four parts. curl downloads a JSON array with one object per certificate. jq -r '.[].name_value' prints the name_value field of each object without quotes. Some entries hold several names separated by newlines, and sort -u removes duplicates. head -50 keeps the output readable.
jq is not installed on my test machine, so I could not run that line here. I ran the same logic with Python 3.12 after downloading the JSON with curl:
curl -s -o crt.json 'https://crt.sh/?q=example.com&output=json'import json
import sys
records = json.load(open(sys.argv[1], encoding="utf-8"))
names = set()
for record in records:
for name in record["name_value"].split("\n"):
names.add(name.strip())
for name in sorted(names)[:50]:
print(name)Save it as crt_names.py and run it with python crt_names.py crt.json. This is the output I observed on 2026-11-08. crt.sh returned HTTP 200 with 85 certificate records, which contained 12 unique names:
*.example.com
AS207960 Test Intermediate - example.com
dev.example.com
emudhra-lab.example.com
example.com
m.example.com
m.testexample.com
products.example.com
[email protected]
support.example.com
[email protected]
www.example.comThree things stand out. The wildcard *.example.com covers any direct subdomain, but it does not tell you which hosts exist. The line "AS207960 Test Intermediate - example.com" is not a hostname. It looks like a subject name copied from a test CA certificate. The entries [email protected] and [email protected] look like email identities from certificates issued for that purpose. A script that treats every line as a hostname will create noise.
The order from sort -u depends on your locale, so it may differ from the Python output. The set of names is what matters.
Export the names from your DNS provider or zone file, one fully qualified name per line, in lowercase, with no trailing dot. Then compare the two lists:
LC_ALL=C sort -u zone-names.txt > zone.sorted
LC_ALL=C sort -u ct-names.txt > ct.sorted
LC_ALL=C comm -13 zone.sorted ct.sortedcomm -13 prints lines that appear only in the second file. Those are names that show up in CT but not in your zone. Wildcards will show up there too, so check them by hand. For the full comparison, remove the [:50] slice from the Python script before writing ct-names.txt. I have not run this step against a real zone, because I do not control the zone for example.com. The comparison works the same way with any list of names.
Some names are legitimate but live outside the zone you exported, for example in another zone at the same provider, or behind a CNAME. Treat "not in my zone file" as a question, not a finding.
| Finding | What it may mean | Next step |
|---|---|---|
| In the zone, certificate still valid | Expected | Record it and move on |
| In CT only, certificate expired | Retired service or old test name | Confirm with the owner, then update the inventory |
| Wildcard entry | Covers names you may not list | Check which of your names it protects |
| Free text or email identity | Not a hostname | Drop it from the inventory, keep it in notes |
| Unknown name, current certificate | Possibly a forgotten service | Ask the owner. Do not probe the host |
| Certificate from a CA your team never uses | Possible misissuance or account problem | Escalate to whoever controls the DNS and CA accounts |
Keep a short record for every review, so the next person can repeat it:
Domain:
Query and date (UTC):
Source and format (crt.sh, JSON):
Certificates returned:
Unique names:
Names matching the zone:
Names needing owner confirmation:
Owner contacted (who, when):Subscribe to a CT monitor for your domain. The CT project notes that monitors check the logs for you and can alert you when a certificate is logged for your domain. That turns a one-time review into an ongoing check.
Read RFC 9162 sections 2 and 8. Section 2 covers the Merkle trees behind the logs. Section 8 covers the parties that check them, including monitors, which are the ones that matter for your domain.
Next, how to write your first security report shows how to turn a lead like this into a short write-up that the asset owner can verify and act on.
OSINT and recon
Learn purpose, proportionality, consent and data minimisation for OSINT, with LGPD and GDPR basics and a one-page research plan template.
OSINT and recon
What OSINT means, how to verify public information, where the ethical limits are, and how to write a short report that shows its evidence.