サイト内検索

Certificate Transparency で未知のサブドメインを検知する — Cert Spotter で始めるサブドメイン監視入門

重岡 正 · Wed, January 6, 2021

自社が管理するルートドメイン配下で、意図しないサブドメインや SSL/TLS 証明書が発行されていないかを気にする場面は増えている。Shadow IT の検知、管理外環境の把握、フィッシングや誤設定への対応など、動機はさまざまだが、共通するのは「自社が把握していないホスト名を、どうやって外部から発見するか」という問いである。

この記事では、Certificate Transparency(以下 CT)ログを中心に、Cert Spotter APIcrt.sh を使ってサブドメインを調査・監視する方法を、Certificate Transparency をあまり触ったことがない読者にも分かる粒度で整理する。DNS プロバイダーの監査ログのような内部視点の監視は、自社アカウントが権威を持つゾーンにしか効かないため、本記事では扱わない。記事内の例示ドメインはすべて example.com を使う。

「サブドメインを登録する」という仕組みは基本的には存在しない

議論の前に、用語を揃えておきたい。

例えば次のような構成を考える。

example.com
├── www.example.com
├── api.example.com
└── dev.example.com

このとき、www.example.comapi.example.com は、どこかに「サブドメイン」というエンティティとして登録されているわけではない。通常は自社が管理する権威 DNS サーバに、次のような DNS レコード を追加することでサブドメインが利用可能になる、というだけである。

  • A
  • AAAA
  • CNAME
  • TXT
  • NS

そのため、

「新しいサブドメインが登録されたことを検知する」

という要件は、素直に読むと厳密には成立しない。実際には、次のような複数の意味が混ざっている。

  1. 新しい SSL/TLS 証明書が発行されたことを検知したい
  2. インターネット上に新しい Web サイトが公開されたことを検知したい
  3. 自社が把握していないサブドメインを発見したい

本記事では、これらのうち外部から観測できる範囲、すなわち Certificate Transparency と Passive DNS を中心に扱う。

Certificate Transparency とは何か

自社が権威 DNS を管理していない領域(子会社の別 DNS、Shadow IT、あるいは第三者による不正な証明書発行)まで含めて「新しく発行された証明書に含まれるホスト名」を追いたい場合は、Certificate Transparency が有効である。

CT ログとは

Certificate Transparency は RFC 6962、そして更新版の RFC 9162 で定義された仕組みで、HTTPS で利用する公開 SSL/TLS 証明書が 認証局(CA)から発行される際に、その証明書の情報を公開の追記専用ログ(CT ログ)へ記録することを CA に要求する仕組みである。主要なブラウザ(Chrome、Safari、Firefox など)は、CT ログに記録されていない公開証明書を有効なものとして扱わないため、事実上、公開向けの証明書はすべて CT ログに載る。

証明書には Subject Alternative Name(SAN)としてホスト名の一覧が含まれる。例えば次のように書かれていれば、CT ログ経由でこれらのホスト名を発見できる可能性がある。

example.com
www.example.com
api.example.com
staging.example.com

この仕組みを利用すると、

自社が把握していないサブドメイン向けに証明書が発行されていないか

を、自社の DNS や CA アカウントに触らずに外部から確認できる。

CT ログと HTTPS サイトの違い

ここで先に重要な注意点を置いておく。

CT ログが検知しているのは「HTTPS サイトが公開されたこと」ではなく、「証明書が発行されたこと」である。

例えば、すでに Wildcard 証明書として、

*.example.com

を発行済みの状態で、

new.example.com

という新しい Web サイトを公開したとする。この場合、new.example.com の公開に合わせて新しい証明書を発行しなければ、CT ログに新しい記録は追加されない。既存の Wildcard 証明書を使い回して HTTPS 化しているだけなら、CT ログから見ると「何も起きていない」ように見える。

したがって、

CT ログ監視 = HTTPS サイト監視

ではない。CT ログは「新しく発行された証明書に含まれるホスト名」までしか教えてくれない、と理解しておくのが安全である。

サブドメイン名に社外秘の情報を含めない

CT ログは、自社が「未知のサブドメインを外部から検知する」ためのデータソースであると同時に、他者にとっても「自社のサブドメインを列挙する」ためのデータソースになる。crt.sh や Cert Spotter は誰でも同じ検索を叩けるため、公開 CA で証明書を発行した瞬間から、そのホスト名は世界中の攻撃者・競合・OSINT ツールに公開されると考えたほうがよい。

そのため、サブドメイン名そのものに顧客企業名・プロジェクトコードネーム・未公開の戦略情報など、社外秘に当たる文字列を埋め込むのは避けたい。例えば次のような命名は、ホスト名そのものがリークになる。

  • 顧客企業名を露出させるもの(例:bigcustomer-portal.example.com
  • 未公開のプロジェクトコードネーム(例:project-nemo.example.com
  • 未公開の M&A や新規事業を示唆する名前(例:newventure-acquisition.example.com
  • 内部システムの構成やバージョンが推測できる名前(例:admin-mysql-primary.example.com

「CT に載せたくない」という理由で Wildcard 証明書やプライベート CA(社内に閉じた認証局)に寄せる手もあるが、前者は本記事の後半で触れる検知の穴を作り、後者はブラウザから公的に信頼されない。命名側で当たり障りのないホスト名にしておく(例:portal-a1.example.com のように内部管理番号だけを使う)ほうが、運用としては素直で安全である。

Cert Spotter API を使う方法

CT ログを継続的に検索する用途では、SSLMate が提供する Cert Spotter API が第一候補になる。SSLMate 側で CT ログを常時監視・インデックスしており、公式 API 経由で証明書を検索できる。

基本的な使い方は次のようになる。

curl -s \
  'https://api.certspotter.com/v1/issuances?domain=example.com&include_subdomains=true&expand=dns_names' \
  | jq -r '.[].dns_names[]' \
  | sort -u

include_subdomains=true を付けると、example.com そのものだけでなく配下のサブドメインを含む証明書もヒットする。expand=dns_names はデフォルトで省略されるフィールドを展開するためのパラメータで、SAN の DNS 名一覧を返してもらうために必要である。

なお、Cert Spotter API は評価用の未認証アクセスも用意されているが、本番運用では API キーの発行Authorization: Bearer <API_KEY> または HTTP Basic Auth)を前提とし、レート制限は SSLMate 側のアカウントで管理される。継続監視で回すなら API キーを取得しておくのが安全である。

Cert Spotter で別ドメインが表示される理由

domain=example.com を指定して先ほどのコマンドを叩くと、次のような結果が返ってくることがある。

*.example.com
example.com
example.edu
example.net
example.org
www.example.com
www.example.edu
www.example.net
www.example.org

example.com を指定したのに、なぜ example.netexample.eduexample.org が混ざるのか」という疑問を持つはずである。

答えは、Cert Spotter が「example.com に一致する証明書」を検索し、その証明書に含まれるすべての DNS 名(SAN)を expand=dns_names で返しているためである。

例えば 1 枚の証明書が、次のような SAN を持っていたとする。

Subject Alternative Names:
 
example.com
www.example.com
example.net
www.example.net
example.org
www.example.org

このとき、example.com が含まれているという理由で、この証明書自体がヒットする。そして expand=dns_names によって、その証明書の SAN すべてが結果に含まれる。

図にすると次のイメージである。

flowchart TB
    A["domain=example.com"] --> B["example.com を含む証明書を検索"]
    B --> C["Certificate の SAN<br/>example.com ← HIT<br/>www.example.com<br/>example.net<br/>example.org<br/>example.edu"]
    C --> D["expand=dns_names"]
    D --> E["SAN をすべて返す"]

つまり、example.netexample.org は「example.com と同一証明書に相乗りしている副産物」であって、example.com 配下のホスト名ではない。監視の観点では、この副産物をフィルタで落とす必要がある。

example.com 配下だけに絞り込む

Cert Spotter の結果から、example.com 配下だけを取り出すには、jqselect で「完全一致 or .example.com で終わるもの」に絞ればよい。

curl -s \
  'https://api.certspotter.com/v1/issuances?domain=example.com&include_subdomains=true&expand=dns_names' \
  | jq -r '.[].dns_names[]
      | select(. == "example.com" or endswith(".example.com"))' \
  | sort -u

期待される出力は次のようになる(example.comRFC 2606 の予約ドメインなので、実サービスと違って api.staging. のようなサブドメインの証明書が発行されていない。そのため出力は最小限になる)。

*.example.com
example.com
www.example.com

endswith(".example.com") に前の . を入れているのがポイントで、これがないと notexample.com のような別ドメインもヒットしてしまう。

汎用的に利用できる Shell スクリプト

同じ処理を複数ドメインに使い回せるように、ドメインを変数化しておくと便利である。

DOMAIN="example.com"
 
curl -s \
  "https://api.certspotter.com/v1/issuances?domain=${DOMAIN}&include_subdomains=true&expand=dns_names" \
  | jq -r --arg domain "$DOMAIN" \
      '.[].dns_names[]
       | select(. == $domain or endswith("." + $domain))' \
  | sort -u

これで、

DOMAIN="example.com"

の部分だけ差し替えれば、別ドメインに対しても同じロジックで一覧を取れる。CI/CD やジョブランナーから叩く場合、DOMAIN を環境変数として渡すだけで済む。

継続監視について

一度検索して終わり、ではなく、

新しい証明書やサブドメインが出現したら通知する

というのが実用的な監視である。ざっくりした構成は次のようになる。

flowchart TB
    A[Cert Spotter] --> B[定期実行]
    B --> C[現在の DNS Names 一覧]
    C --> D[前回の一覧と diff]
    D --> E[新しいサブドメイン]
    E --> F["Slack / Teams / SIEM"]

実装先の選択肢としては、次のような場所が現実的である。

例えば、前回と今回で次のように差分が出た場合を考える。

前回
 
api.example.com
example.com
www.example.com
今回
 
api.example.com
example.com
new.example.com
www.example.com

このとき、

+ new.example.com

を検知して Slack に通知する、という運用になる。State は S3 や Cloud Storage、あるいは Git 管理下のファイルに置くのが素直で、ここは既存のインフラに合わせればよい。

after を使った差分取得

Cert Spotter API には、前回取得した位置以降の新しい issuance だけを取るためのカーソル型のパラメータが用意されている。SSLMate CT Search API v1 では次のような使い方になる。

  • 各 issuance には id フィールドがある
  • 次回のリクエストで after=<前回レスポンスの末尾 issuance の id> を付ける
  • そのカーソル以降に新しく発見された issuance だけが返ってくる

擬似的な流れは次の通りである。

flowchart TB
    A[前回取得位置] --> B[新しい Certificate だけ取得]
    B --> C[dns_names を確認]
    C --> D[未知のサブドメインなら通知]

コマンド例としては、次のように after を付ける(LAST_ID は前回保存しておいたカーソル)。

DOMAIN="example.com"
LAST_ID="$(cat ./certspotter-cursor)"
 
curl -s \
  "https://api.certspotter.com/v1/issuances?domain=${DOMAIN}&include_subdomains=true&expand=dns_names&after=${LAST_ID}"

新しい issuance が無い場合、Cert Spotter は空配列と Retry-After ヘッダを返してくる。ポーリング間隔はこの Retry-After に従うのが行儀のよい書き方である。

毎回全件取ってから diff する方式でも動くが、量が増えると帯域とレート制限で不利になる。継続監視の本命は after によるカーソル方式だと考えておいてよい。仕様は変わり得るので、実装時には必ず 公式ドキュメント を最新版で確認してほしい。

手軽な確認手段としての crt.sh

Cert Spotter を主線に据えたうえで、もう 1 つ触れておきたいのが crt.sh である。Sectigo が運営している公開サービスで、SQL ライクな検索と JSON API の両方を提供している。UI から 1 発叩いて手元で確認したい、といった単発の調査用途では今でも便利だが、後述するとおり 502 が頻発する時期があり、自動監視の主線としては勧めにくい。位置づけとしては「Cert Spotter を組む前の下見」「Cert Spotter で気になった結果のクロスチェック」あたりに寄せておくのが安全である。

Web UI で試す場合は、次のように % をワイルドカードとして検索する。

%.example.com

同じ検索を JSON API で叩くには、次のように output=json を付ける(% は URL エンコードして %25)。

curl -s \
  'https://crt.sh/?q=%25.example.com&output=json'

サブドメイン一覧だけを取り出したい場合は、jq でパースして sort -u で重複排除する。

curl -s \
  'https://crt.sh/?q=%25.example.com&output=json' \
  | jq -r '.[].name_value' \
  | sort -u

name_value には SAN と Common Name が改行区切りで入っていることがある。厳密に 1 行 1 ホスト名にしたいなら、jq -r '.[].name_value | split("\n")[]' のようにしておくとよい。

crt.sh 利用時の注意点

crt.sh は非常に便利だが、API としての SLA が保証されたサービスではない。ピーク時間帯やクロールが集中したタイミングでは、次のような HTTP エラーが頻発する。

502 Bad Gateway

このとき、curl | jq のパイプで気づかずに JSON パースを続けると、次のようなエラーが出る。

jq: parse error: Invalid numeric literal

これは jq 自体のバグではなく、

flowchart LR
    A[curl] --> B[HTML のエラーページ]
    B --> C[jq]

という流れになり、JSON ではなく HTML のエラーページを jq が解析しようとして落ちているだけである。

トラブルシュートは、-i オプションでヘッダごと確認するのが早い。

curl -i \
  'https://crt.sh/?q=%25.example.com&output=json'

これで、

  • HTTP status(200 か 5xx か)
  • Content-Type(application/jsontext/html か)
  • 実際のレスポンス本文

を切り分けられる。200 以外や text/html が返っている場合は、少し時間を空けてから再実行するか、そもそも自動化の本線は Cert Spotter API に寄せて crt.sh は下見と手元確認だけに使う、という運用に切り替えるのが現実的である。

Wildcard 証明書の注意点

ここまで Cert Spotter や crt.sh の話を進めてきたが、CT ログベースの監視には構造的な穴がある。それが Wildcard 証明書である。

例えばすでに、

*.example.com

の Wildcard 証明書を発行済みだったとする。この証明書を使い回して、後から次のホスト名を公開しても、

app1.example.com
app2.example.com
secret.example.com

CA からの新しい証明書発行は発生しないため、Certificate Transparency ログには何も追加されない。

つまり、Wildcard 証明書を運用している範囲では、

  • CT ログを見ても新しいサブドメインは分からない
  • Cert Spotter・crt.sh・その他 CT 検索ツールも同様

という制約がある。ここは CT だけに寄せた監視の弱点として、明示的に受け入れておく必要がある。

Wildcard DNS も外側からは見えない

DNS 側にも似た構造の穴がある。権威 DNS で Wildcard DNS レコード が設定されていると、

*.example.com → 203.0.113.10

のような 1 本のレコードで、

foo.example.com
bar.example.com
anything.example.com

がすべて名前解決できてしまう。個別の A レコードや CNAME レコードは追加されないため、Wildcard 証明書とちょうど同じ理屈で、外部の CT や DNS ゾーン列挙からは新しいホスト名の存在は見えない。この領域を可視化するには、後述する Passive DNS のように「実際にリゾルバが観測したクエリと応答」を集めているデータソースが必要になる。

推奨監視構成

以上を踏まえると、Certificate Transparency 単体に寄せず、外側から観測できるデータソースを重ねるのが現実的である。全体像は次のようなイメージになる。

flowchart TB
    Root[example.com]
    Root --> CT[CT Log]
    Root --> PDNS[Passive DNS]
    CT --> CS[Cert Spotter]
    PDNS --> PA[外部観測アーカイブ]
    CS --> INV[サブドメイン一覧]
    PA --> INV
    INV --> ALLOW[Allowlist と比較]
    ALLOW --> Known[既知]
    ALLOW --> Unknown[未知]
    Unknown --> Alert["Slack / SIEM 通知"]

CT は「証明書が発行された事実」を、Passive DNS は「実際に名前解決された事実」を拾うため、Wildcard 証明書と Wildcard DNS のそれぞれの穴を互いに補い合う関係にある。運用側のコストは上がるが、単一手段では検知できないホスト名を補える。

各方式を比較する

外側から見える主な方式を並べると、次のような整理になる。

方法検知対象リアルタイム性Wildcard 対応特徴
Certificate Transparency証明書発行高い外部から確認可能
Cert SpotterCT ログ/API高い自動監視に向いている
crt.shCT ログ検索手軽だが 502 が頻発、下見・手元確認向き
Passive DNSDNS 観測外部で観測されたホストを発見可能

Wildcard 対応が になっている理由は、Wildcard 証明書や Wildcard DNS の下では新規ホスト名の発行・追加イベントが発生しないため、そもそも監視対象のイベントが存在しないことに由来する。Wildcard を積極的に使っている環境では、Passive DNS の重要度が相対的に上がる。

結論

用途別に整理すると、次のように使い分けるのが素直である。

新しい SSL/TLS 証明書を外部から検知したい

Certificate Transparency をベースにする。

Certificate Transparency

ツールとしては、

Cert Spotter(自動監視の主線)
crt.sh(下見・手元確認)

の順で使い分けると素直である。継続監視には Cert Spotter API の after カーソルが向く。

未知のサブドメインをできるだけ網羅的に見つけたい

単一手段ではなく、次の組み合わせにする。

Certificate Transparency
+
Passive DNS

Wildcard 証明書で CT が沈黙する領域は Passive DNS 側で拾い、Passive DNS では観測されないニッチな用途は CT で拾う、という関係である。

最後にもう一度強調しておきたいのは次の 1 点である。

「Certificate Transparency で発見できるのは証明書が発行されたホスト名であり、現在存在するすべての HTTPS サイトやサブドメインではない」

CT ログ監視を導入すると「自社のサブドメインは全部見えている」と誤解しやすいが、Wildcard 証明書と Wildcard DNS が絡む領域は CT からは見えない。この前提を運用側で共有した上で、CT と Passive DNS を重ねていくのが、外側から見た現実的なサブドメイン検知のあり方である。

以上、Certificate Transparency と Cert Spotter・crt.sh を使った未知サブドメインの外形検知、Wildcard 証明書と Wildcard DNS で発生する見落とし、そして Passive DNS と組み合わせた推奨構成を整理した、現場からお送りしました。

参考情報