npx skills add ...
npx skills add useosint/osint-skills --skill find-hidden-subdomains
Enumerate an organisation's subdomains and sibling domains from Certificate Transparency logs and passive DNS, without sending traffic to the target. Covers crt.sh and CT log queries, certificate SAN fields, subfinder and amass, and newly issued TLS certificates. Use when looking for staging, dev, admin or VPN hosts, mapping the full hostname footprint of a domain, or spotting infrastructure a company forgot it had. Applies to attack-surface mapping, vendor and supply-chain security review, brand-infringement discovery, and M&A technical diligence. Reference at useosint.com/skills/find-hidden-subdomains.
npx skills add useosint/osint-skills --skill find-hidden-subdomains
The hosts an organization forgot about are the ones worth finding, and most of them announced themselves the moment someone requested a TLS certificate. Certificate Transparency turns that into a searchable, permanent, historical index — free and completely passive. The beginner's mistake is treating a CT hit as a live host: most of what you pull back does not resolve, and that is information too, not noise.
| You hold | Start with | Why |
|---|---|---|
| An apex domain | crt.sh wildcard query | Broadest free coverage, includes long-dead names |
| A domain behind a wildcard cert | Passive DNS, then archives | CT will only show you *.example.com |
| A guess at a naming convention | CT to learn the convention, then a wordlist | Learn the pattern before brute-forcing anything |
| An org name, not a domain | Certificate search by subject organization | Finds domains you did not know they owned |
| A cert you already have | Its SANs, then its serial and issuer | SANs give siblings; serial finds the exact cert elsewhere |
| A need for current hosts only | Resolve the candidate list | CT is historical by nature; DNS is the liveness oracle |
CAs submit every certificate they issue to append-only, cryptographically verifiable public logs. The log returns a Signed Certificate Timestamp, and mainstream browsers refuse to trust a publicly-trusted certificate that does not carry SCTs from qualified logs. So participation is not voluntary: if the certificate works in a browser, it is in the logs.
Three consequences that matter to you:
The web UI takes ?q=, where % is a wildcard, and output=json gives you
machine-readable results.
The q parameter is an identity search: it matches domain names, and it also
matches subject fields such as the organization name, which is how you find
domains the brief never mentioned.
Useful JSON fields: name_value (all names on the cert, newline-separated),
common_name, issuer_name, not_before / not_after, serial_number,
entry_timestamp, and id (open https://crt.sh/?id=<id> to read the cert).
crt.sh also exposes its database read-only over Postgres — host crt.sh, port
5432, user guest, database certwatch. That lets you write real SQL against
the certificate index instead of paginating JSON. It is a shared free service:
keep queries bounded and expect to be cut off if you hammer it.
Alternatives and other CT front-ends, with their coverage and access models, are in reference/subdomain-sources.md.
Browsers ignore the Common Name; the subjectAltName extension holds the real
list of covered hostnames. Read it as a statement about what one team deployed
together:
jira, owa, vpn, *.corp.example.com,
hostnames with rack or datacenter codes, and product codenames that never
shipped publicly.*.example.com covers every subdomain and names none of them. An org that
switched to a wildcard, typically alongside DNS-01 ACME automation, effectively
went dark in CT from that point. When you see a wildcard:
read-deleted-pages,
google-like-a-spy).a.b.example.com needs its own), and
anything on a different apex.CT gives you history. Passive DNS gives you observed reality. Wordlists give you names nobody published. Use all three, and keep them labelled by origin, because the confidence you can assign depends on where a name came from.
Resolution turns candidates into three buckets: resolves to an address, resolves to a CNAME (read the target — it names the vendor, and a dangling one is a takeover candidate to report), or does not resolve at all. Dead names are still findings: they date the infrastructure, expose naming conventions, and sometimes resolve again later.
Detect wildcard DNS before you trust any brute-force result. Resolve a
random name that cannot exist, such as zzq7x-nonexistent.example.com. If it
answers, the zone resolves everything and your entire brute-force list is
false positives.
Draw the line clearly: querying CT, passive DNS, search engines and archives
never touches the target. Resolving candidate names through a public recursive
resolver is effectively invisible but does generate lookups. Wordlist
brute-forcing is high-volume DNS traffic, visible to the target's DNS provider
and sometimes rate-limited or blocked. HTTP-probing the results with a tool like
httpx is active — you are connecting to the target's servers. Flag
brute-forcing and probing in your scope notes, and skip them if the engagement
is passive-only.
Do not treat all discovered hosts as equal. dev, staging, uat, vpn,
legacy, old, and admin panels carry very different risk and follow-up than
www. The triage table is in
reference/name-pattern-triage.md.
not_before against the domain's registration and transfer history via
who-owns-this-domain.Record for every host: where the name came from, the cert id or passive-DNS
source, the not_before date, and the resolution result with a timestamp.
Target: example-health.test, scope is passive-only.
crt.sh with %25.example-health.test returns 61 unique names. Deduplicating
precert/cert pairs cuts it to 38. Three are interesting immediately:
vpn-uat.example-health.test, jira.internal.example-health.test, and
billing-legacy.example-health.test.
One cert carries 44 names across nine unrelated companies — a managed hosting provider's bundle. Discard the 43 that are not the target's. That is the dead end, and it would have inflated the report by more than the real findings.
Resolving the 38: 12 answer. jira.internal does not resolve publicly, but its
existence in a cert confirms an internal naming convention and a Jira instance —
worth noting even though there is no host to look at. vpn-uat resolves to an
appliance vendor's netblock, which goes to find-exposed-servers.
An identity search on the organization name from one of the certs returns two certs
for examplehealth-group.test, a domain nobody mentioned in the brief. WHOIS via
who-owns-this-domain shows the same registrant organization. That is the pivot
that expanded the engagement.
Grade: 12 resolving hosts confirmed, jira.internal probable (real name,
not publicly reachable), the sibling domain probable pending a corporate
filing.
| New selector | Goes to |
|---|---|
| Live hostnames and their IPs | find-exposed-servers |
| Sibling and acquired domains | who-owns-this-domain, who-really-owns-it |
| Organization name from a cert subject | x-ray-a-company |
| Dead hostnames with historical content | read-deleted-pages |
| Dev/staging hostnames, repo-looking names | secrets-in-git-history |
| Indexed files on newly found hosts | google-like-a-spy |
| A large host/domain/IP set to structure | graph-the-network |
Reading CT logs and passive DNS is publishing-by-design: the data exists to be read. Two limits are real. First, free services like crt.sh are shared infrastructure — heavy automated querying gets you blocked and is discourteous; use bounded queries and cache results. Second, DNS brute-forcing and HTTP probing send traffic to the target and, depending on volume and jurisdiction, can be argued as unauthorized activity; keep them inside written scope per ../../ETHICS.md. Discovering a dangling CNAME or an exposed staging host obliges you to report it, not to test it.
subfinder -d example.com -all -silent -o passive.txt # aggregates many passive sources
amass enum -passive -d example.com # different source mix, slower
dnsx -l passive.txt -a -resp -silent # resolve candidates to A records