2026年10月7日時点で、ShifterはOxylabsやBright Dataを上回る、最大規模の米国ライブIPプールを保有しており、しかもコストはその一部で済みます。Shifterは現在、稼働中の米国IPの中で最大のプールを保有しています

ベンチマークを見る

パッシブ脅威インテリジェンス:ターゲットに触れずに収集する

ドメインにパケットを送らずに脅威インテリジェンスを収集する方法:証明書ログ、RDAP、アーカイブ、パッシブDNS、検証済みPythonとその限界について

エンジニアリング · 2026年11月1日に更新 · 3分で読めます
CT logsRDAPWaybackpDNSスキャンデータあなたターゲット
情報源はずっと前にターゲットを確認していました。アンバーの線は、決して送信しないリクエストです。
ナレッジ
このページについて
  1. パッシブとは何を意味するか
  2. 確認する価値のある情報源
  3. 小さなパッシブコレクター
  4. パッシブがパッシブでなくなる境界
  5. 結果をどう扱うか
  6. 結論
  7. 情報源と参考文献

脅威インテリジェンスチームが何かをスキャンする前に、他者がすでに保持している記録からドメインについて驚くほど多くのことを学べる。証明書ログにはホスト名が記載され、登録データには日付やネームサーバーが記載され、ウェブアーカイブには過去に公開されていた内容が示される。セキュリティチームにとって、このパッシブな段階は作業の中で最も安価かつ最も静かな部分であり、どんな攻撃者でも見えるものを示している。

このガイドでは、主なパッシブ情報源、小さなテスト済みコレクター、そして「パッシブ」が成立しなくなる境界を扱う。自組織に対して、あるいは許可されたスコープ内で使用すること。

パッシブとは何を意味するか

パッシブな収集とは、リクエストがすでにデータを保持している第三者に送られ、対象のサーバーには決して送られないことを意味する。MITRE ATT&CKは攻撃者側の手法をReconnaissanceの下で説明しており、「自由に利用可能な技術データベース」を検索すること(T1596)が含まれ、DNSとパッシブDNS、WHOIS、デジタル証明書、CDN、スキャンデータベースについてのエントリがある。

MITREはまた、なぜ防御側がこれを気にかけるべきかも説明している。この活動は対象組織の可視性の外で行われる可能性があり、検知が困難であり、予防的な制御で容易に緩和することができないとしている。誰かが公開ログを読むのを止めることはできない。できることは、先に自分で確認し、ログが明らかにする情報を減らすことだ。

確認する価値のある情報源

情報源得られるもの限界
Certificate Transparencyログ発行されたTLS証明書上のホスト名名前は示すが、稼働中かどうかは不明。ワイルドカードはサブドメインを隠す
RDAPレジストラ、日付、ネームサーバー個人データは通常編集済み
ウェブアーカイブ古いページ、URL、ホストキャプチャされたものだけで、しばしば数年前のもの
パッシブDNSある名前が解決されていたIPとその時期ほとんどが商用で、カバレッジはさまざま
インターネットスキャンデータベース他者によって確認された開いているポートとバナースナップショットであり、古くなっている可能性がある

Certificate Transparencyは最も豊富な無料情報源だ。RFC 9162は、誰もが認証局を監査できるようにTLS証明書の「存在を公に記録する」プロトコルを説明しており、ログは追記専用だ。つまり、staging.internal-tool.example.comに対する証明書は、そのホストがたとえ1週間しか存続しなかったとしても、可視のまま残り続けるということだ。

RDAPはWHOISに代わる構造化された後継プロトコルだ。ICANNによれば、2025年1月28日以降、ジェネリックトップレベルドメインのレジストリとレジストラはRDAPを提供しなければならず、WHOISを提供する義務はなくなった。そのため、新しいツールはRDAPから始めるべきだ。

OSINTとは何か、その法的境界がどこにあるかに関する我々のガイドでは、人物に関する情報源を含むより広い分野を扱っているが、本ガイドではそれを扱わない。

小さなパッシブコレクター

以下のスクリプトは、ドメインについて3つの第三者サービスに問い合わせるが、ドメイン自体には決して接続しない。Python 3.9以降で動作し、必要なのはrequestsのみだ。無料の公開サービスはしばしばダウンしているため、ソースがダウンしていても続行する。このテスト中、crt.shとWayback Machineはそれぞれ数分間にわたってエラーとタイムアウトを返した。

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'}")

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

このコードの2つの詳細はテストから得られたものだ。証明書ログにはホスト名以上のものが含まれている。初期バージョンでは単純な接尾辞チェックを使用しており、スペースを含む証明書ラベルと、なりすまし名m.testexample.comを通過させてしまった。これが、in_scopeがサブドメイン全体を照合する理由だ。もう一つの詳細は、各情報源が異なるものを見るため、一つを信頼するのではなく比較すべきだということだ。古いアーカイブにのみあるホスト名はすでに消えている可能性があり、最近の証明書にのみあるものは新しい可能性がある。

パッシブがパッシブでなくなる境界

その境界は見た目より越えやすい。

  • 見つけたURLを開くこと。 結果の中の稼働中のホスト名を訪問すると、対象に自分のIPからのリクエストを送ることになる。
  • ライブリソースを含むアーカイブされたページ。 保存されたページが、元のサイトから画像やスクリプトを読み込もうとすることがある。外部読み込みを無効にするか、生のレスポンスを読むこと。
  • DNSルックアップ。 リゾルバを通じたクエリは、対象の権威ネームサーバーに届く可能性がある。過去のパッシブDNSデータはそうならない。
  • 自分のクエリは情報源に見えている。 問い合わせたサービスは、自分が何を調べているかを知る。機密性の高い調査では、これが何を明らかにするかを考慮し、利用規約とレート制限を確認すること。

結果はリードとして扱うこと。記録は古くなり、証明書はホストより長く存続し、共有ホスティングはIP履歴を誤解させる。

結果をどう扱うか

防御側にとって、結果は外部の世界がすでに見ることができるもののリストだ。賢明な手順は次の通り:

  1. 自組織の資産インベントリと照合する。 所有者に紐付けられないホスト名こそが発見事項だ。
  2. 自分たちの名前が何を漏らしているか確認する。 公開ログにあるvpn-oldやjenkins-testのような名前は攻撃者を助ける。適切な場合には、内部命名にワイルドカード証明書を検討すること。ただし、それ自体のリスクも考慮すること。
  3. 繰り返し実行し、新しいものに警告する。 継続的な外部攻撃対象領域モニタリングに関する我々のガイドでは、これを所有者が設定された差分に変える方法を示している。
  4. 意図的にアクティブなチェックに移行する。 自分の資産は直接検証すること。自組織外の何かについては、まず許可を得ること。脅威リサーチが、特定の国にいる実際の訪問者に不審なサイトが何を表示しているかを見る必要がある場合、それは独自のルールを持つアクティブな作業であり、脅威インテリジェンスのためのレジデンシャルプロキシに関する我々の記事で扱っている。

結論

パッシブな収集は、ほぼコストをかけずに、かつ対象に気づかれることなく、ドメインについて有用な全体像を与えてくれる。証明書ログ、RDAP、アーカイブから始め、それらを比較し、見つけたものをリードとして扱い、自分の作業がパッシブでなくなる瞬間について正直であること。まず自分自身に対して実行すること。なぜなら、他の誰でもそれができるからだ。

情報源と参考文献

よくある質問

パッシブ脅威インテリジェンスとは何ですか。

証明書ログ、登録記録、ウェブアーカイブ、パッシブDNSなど、第三者がすでに保有しているデータから、対象のドメインや組織に関する情報を収集することであり、対象自身のシステムへトラフィックを送ることはありません。

パッシブな偵察は本当に見えないものなのですか。

対象にとってはおおむねそうです。対象のログにはあなたのクエリは残りませんが、第三者サービスはそれを確認しており、あなたの検索履歴を見た者は、あなたが何に関心を持っていたかを知ることになります。見つけたURLに実際にアクセスする、あるいは対象のネームサーバーに到達するDNSルックアップを行うといった行為は、もはや厳密にはパッシブとは言えません。

パッシブな収集は合法ですか。

公開記録を読むことは、一般的にアクティブスキャンよりリスクは低いですが、それですべてが解決するわけではありません。各情報源には独自の利用規約があり、人に関するデータはプライバシー法の対象となる場合があり、結果をどう使うかについても許可が必要です。法務チームに確認し、他の組織に関する作業は合意済みの範囲内に留めてください。

パッシブ収集からアクティブ収集へ移るべきなのはどのような時ですか。

現在何が真実かを知る必要がある時です。パッシブな情報源は数か月、あるいは数年前のものである可能性があります。候補のリストができた時点で、防御側は自らの資産を直接確認でき、第三者に関してその段階に進む場合には明示的な許可が必要です。

始める準備はできていますか?

Shifterのレジデンシャルプロキシをお試しください。IP 205M+件、195+カ国、$0.10/GBから。

始める