As of October 7, 2026, Shifter has the largest pool of live US IPs, more than Oxylabs and Bright Data, at a fraction of the cost.Shifter now has the largest pool of live US IPs

See benchmarks

Passive Threat Intelligence: Collecting Without Touching the Target

How to gather threat intelligence on a domain without sending it a packet: certificate logs, RDAP, archives and passive DNS, with tested Python and the limits.

Engineering · Updated November 1, 2026 · 7 min read
CT logsRDAPWaybackpDNSscan datayoutarget
The sources saw the target long ago. The amber line is the request you never send.
Knowledge
On this page
  1. What passive means
  2. The sources worth checking
  3. A small passive collector
  4. Where passive stops being passive
  5. What to do with the results
  6. The bottom line
  7. Sources and references

Before a threat intelligence team scans anything, it can learn a surprising amount about a domain from records other parties already keep. Certificate logs list its hostnames, registration data gives its dates and nameservers, and web archives show what it used to publish. For security teams, this passive stage is the cheapest and quietest part of the job, and it shows what any attacker can see too.

This guide covers the main passive sources, a small tested collector, and where “passive” stops being true. Use it on your own organisation or within an authorised scope.

What passive means

Passive collection means your requests go to a third party that already holds the data, and never to the target’s servers. MITRE ATT&CK describes the attacker’s version under Reconnaissance, including searching “freely available technical databases” (T1596), with entries for DNS and passive DNS, WHOIS, digital certificates, CDNs and scan databases.

MITRE also explains why defenders should care. It says this activity can take place outside the visibility of the target organisation, which makes it hard to detect, and that it cannot be easily mitigated with preventive controls. You cannot block someone reading a public log. What you can do is look first and reduce what the logs reveal.

The sources worth checking

SourceWhat it gives youIts limits
Certificate Transparency logsHostnames on issued TLS certificatesShows names, not whether they are live; wildcards hide subdomains
RDAPRegistrar, dates, nameserversPersonal data is usually redacted
Web archivesOld pages, URLs and hostsOnly what was captured, often years old
Passive DNSWhich IPs a name resolved to, and whenMostly commercial, and coverage varies
Internet scan databasesOpen ports and banners seen by someone elseSnapshots, and may be out of date

Certificate Transparency is the richest free source. RFC 9162 describes a protocol for “publicly logging the existence” of TLS certificates so that anyone can audit certificate authorities, and logs are append-only. That also means a certificate for staging.internal-tool.example.com stays visible, even if the host only lasted a week.

RDAP is the structured replacement for WHOIS. ICANN says that since 28 January 2025, generic top-level domain registries and registrars must provide RDAP and are no longer required to provide WHOIS, so new tooling should start there.

Our guide to what OSINT is and where its legal boundaries sit covers the wider discipline, including sources about people, which this guide leaves out.

A small passive collector

The script below asks three third-party services about a domain and never connects to the domain itself. It runs on Python 3.9 and later and needs only requests. It keeps going when a source is down, because free public services often are: while testing this, crt.sh and the Wayback Machine each returned errors and timeouts for several minutes at a time.

from __future__ import annotations

import sys
import time

import requests

TIMEOUT = (10, 60)
HEADERS = {"User-Agent": "passive-recon-demo/1.0"}


def get_json(url, params=None):
    """Every request goes to a third-party source, never to the target itself."""
    for attempt in range(3):
        try:
            r = requests.get(url, params=params, headers=HEADERS, timeout=TIMEOUT)
            r.raise_for_status()
            return r.json()
        except requests.RequestException:
            if attempt == 2:
                raise
            time.sleep(10 * (attempt + 1))  # free public services go down often


def in_scope(host, domain):
    """The domain or a real subdomain of it; CT names also hold labels and lookalikes."""
    return " " not in host and (host == domain or host.endswith("." + domain))


def certificate_names(domain):
    """Hostnames from Certificate Transparency logs, through crt.sh."""
    rows = get_json("https://crt.sh/", {"q": f"%.{domain}", "output": "json"})
    names = {n.strip().lower() for row in rows for n in row["name_value"].splitlines()}
    return sorted(n for n in names if "*" not in n and in_scope(n, domain))


def archived_hosts(domain, limit=500):
    """Hostnames the Wayback Machine has captured, without loading any live page."""
    rows = get_json("https://web.archive.org/cdx/search/cdx", {
        "url": domain, "matchType": "domain", "output": "json",
        "fl": "original", "limit": limit,
    })
    urls = [row[0] for row in rows[1:]]  # the first row is the header
    hosts = {u.split("//")[-1].split("/")[0].split(":")[0].lower() for u in urls}
    return sorted(h for h in hosts if in_scope(h, domain))


def registration(domain):
    """Registrar and key dates from RDAP, the structured successor to WHOIS."""
    data = get_json(f"https://rdap.org/domain/{domain}")
    dates = {e["eventAction"]: e["eventDate"][:10] for e in data.get("events", [])}
    return {"registered": dates.get("registration"), "expires": dates.get("expiration"),
            "nameservers": sorted(n["ldhName"].lower() for n in data.get("nameservers", []))}


def collect(domain):
    sources = {"certificates": certificate_names, "archive": archived_hosts,
               "rdap": registration}
    found, failed = {}, {}
    for name, fetch in sources.items():
        try:
            found[name] = fetch(domain)
        except (requests.RequestException, ValueError, KeyError) as exc:
            failed[name] = type(exc).__name__  # a source being down is a result too
    return found, failed


if __name__ == "__main__":
    domain = sys.argv[1] if len(sys.argv) > 1 else "example.com"
    found, failed = collect(domain)
    hosts = set(found.get("certificates", [])) | set(found.get("archive", []))
    only_ct = set(found.get("certificates", [])) - set(found.get("archive", []))
    only_archive = set(found.get("archive", [])) - set(found.get("certificates", []))
    print(f"{domain}: {len(hosts)} hostnames seen by third parties")
    print(f"  in certificates only: {sorted(only_ct)[:6]}")
    print(f"  in the archive only:  {sorted(only_archive)[:6]}")
    print(f"  registration: {found.get('rdap')}")
    print(f"  sources unavailable: {failed or 'none'}")

Output for example.com:

example.com: 7 hostnames seen by third parties
  in certificates only: ['dev.example.com', 'emudhra-lab.example.com', 'm.example.com', 'products.example.com', 'support.example.com']
  in the archive only:  []
  registration: {'registered': '1995-08-14', 'expires': '2027-08-13', 'nameservers': ['elliott.ns.cloudflare.com', 'hera.ns.cloudflare.com']}
  sources unavailable: none

Two details in that code came from testing. Certificate logs hold more than hostnames: an early version used a plain suffix check and let through a certificate label with spaces in it and a lookalike, m.testexample.com. That is why in_scope matches a whole subdomain. The other detail is that each source sees different things, so compare them rather than trusting one. A hostname only in old archives may be gone, and one only in recent certificates may be new.

Where passive stops being passive

The line is easier to cross than it looks.

  • Opening a URL you found. Visiting a live hostname from your results sends the target a request from your IP.
  • Archived pages with live resources. A saved page can still try to load images or scripts from the original site. Disable external loading, or read the raw response.
  • DNS lookups. A query through a resolver can reach the target’s authoritative nameservers. Historical passive DNS data does not.
  • Your queries are visible to the source. The service you ask learns what you are looking at. For sensitive investigations, consider what that reveals, and read its terms and rate limits.

Treat results as leads. Records go stale, certificates outlive hosts, and shared hosting makes IP history misleading.

What to do with the results

For defenders, the result is a list of what the outside world can already see. A sensible routine:

  1. Compare against your asset inventory. A hostname you cannot match to an owner is the finding.
  2. Check what your names give away. Names like vpn-old or jenkins-test in public logs help attackers. Consider wildcard certificates for internal naming where they are appropriate, while weighing their own risks.
  3. Repeat it, and alert on what is new. Our guide to continuous external attack surface monitoring shows how to turn this into a diff that has owners.
  4. Move to active checks deliberately. Verify your own assets directly. For anything outside your organisation, get authorisation first. Where threat research needs to see what a suspicious site shows real visitors in a given country, that is active work with its own rules, covered in our piece on residential proxies for threat intelligence.

The bottom line

Passive collection gives you a useful picture of a domain for almost no cost and without alerting it. Start with certificate logs, RDAP and archives, compare them, treat what you find as leads, and be honest about the moment your work stops being passive. Run it on yourself first, because anyone else can.

Sources and references

Frequently asked questions

What is passive threat intelligence?

Collecting information about a domain or organisation from data that third parties already hold, such as certificate logs, registration records, web archives and passive DNS, without sending any traffic to the target's own systems.

Is passive reconnaissance really invisible?

To the target, mostly. The target's logs do not show your query, but the third-party service does see it, and anyone who sees your search history learns what you were interested in. Actions such as loading a live URL you found, or a DNS lookup that reaches the target's nameservers, are no longer strictly passive.

Is passive collection legal?

Reading public records is generally lower risk than active scanning, but it does not settle everything. Each source has its own terms, data about people can fall under privacy law, and what you do with the results still needs authorisation. Check with your legal team, and keep work on other organisations inside an agreed scope.

When do you need to move from passive to active collection?

When you need to know what is true now. Passive sources can be months or years old. Once you have a list of candidates, a defender can check their own assets directly, and for third parties that step needs explicit authorisation.

Ready to get started?

Try Shifter's residential proxies, 205M+ IPs, 195+ countries, from $0.10/GB.

Get Started