年1回の脆弱性診断は有効な取り組みです。ただ、その診断の対象リストを作っているのは自社です。攻撃者は社内の管理台帳を見ません。インターネットから到達できるものすべてが対象です。
この記事は、外部公開資産の棚卸し=ASM(Attack Surface Management)を、無料で使えるツールと表計算1枚で始めるための実務手順をまとめたものです。専任のセキュリティ部門がない組織を想定しています。
- 対象読者: 情シス担当 / インフラ・運用担当 / 中小規模組織のセキュリティ担当
- ゴール: 「外から見えている自社」の一覧と、その差分を毎月検知する仕組みを持つ
⚠️ 実施前の前提(重要)
- DNS・RDAP・証明書透明性(CT)ログの参照は公開情報の照会なので、通常問題になりません。
- 一方、ポートスキャンなどの能動的な到達性確認は、自社が管理権限を持つ資産に対してのみ実施してください。
- 共用ホスティングやクラウド(AWS / Azure / GCP 等)では、事業者の利用規約でスキャンの許可範囲や事前申請の要否が定められています。自分の契約している事業者の規約を必ず確認してから実行してください。
- 委託先が運用している環境をスキャンする場合は、契約上の合意を先に取ります。
1. なぜ「診断だけ」では足りないのか
診断が無意味なのではありません。役割が違う道具を、違う目的に使ってしまうのが問題です。
| 観点 | 脆弱性診断 | ASM |
|---|---|---|
| 対象 | 事前に指定したURL / IP | 外から見えるもの全部(発見から始まる) |
| 深さ | 深い | 浅い〜中程度 |
| 頻度 | 点(年1回など) | 継続 |
| 起点 | 社内の管理台帳 | 公開情報(DNS / CT / RDAP など) |
構造的に埋まらない穴は4つあります。
- スコープ — 台帳に載っていない資産は、最初から対象外。「指摘ゼロ」は「提出したリストの範囲で問題なし」という意味であって、会社全体が安全という意味ではありません。
- 時間軸 — 診断は特定日のスナップショットです。診断日の翌日に公表された重大な脆弱性は、次の診断まで誰も外側から見ていません。
- 境界機器 — VPN装置・FW・リモートアクセス製品は、インターネットに面しているうえ、業務が止まるため更新が後回しになりやすく、「セキュリティ製品だから」と無意識に対象リストから外されがちです。管理画面が意図せず外部公開されたままの例も珍しくありません。
- 所有者不在 — 脆弱性が見つかっても、担当部署・契約先・停止してよいかの判断者が分からなければ対応は止まります。
診断が「渡された鍵を一つずつ点検する作業」なら、ASMは「そもそも部屋がいくつあるのか数え直し続ける作業」です。
2. Step1: 台帳を見ずに、外から洗い出す
ここが一番のコツです。社内台帳を起点にしない。まず外部情報だけで一覧を作り、あとで台帳と突き合わせて差分を出します。この差分がそのまま最初のリスクリストになります。
以下、Linux / WSL / macOS で dig curl jq openssl nmap が入っている前提です。
# Ubuntu / WSL の場合
sudo apt update && sudo apt install -y dnsutils curl jq openssl nmap
2-1. 証明書透明性ログからホスト名を集める(初手・費用対効果が最大)
サーバー証明書は発行された事実が公開ログに記録されます。ここを引くと、社内の誰も覚えていないホスト名が出てくることがよくあります。
DOMAIN=example.com
curl -s --retry 3 --retry-delay 5 "https://crt.sh/?q=%25.${DOMAIN}&output=json" |
jq -r '.[].name_value' |
sed 's/^\*\.//' |
tr 'A-Z' 'a-z' |
sort -u > ct_hosts.txt
wc -l ct_hosts.txt
つまずきどころ
- crt.sh は混雑時に 502 やタイムアウトを返します。
--retryを付け、時間帯をずらして再実行します。 -
name_valueは1レコードにSANの複数ホスト名が改行区切りで入っています。jq -rが実際の改行に展開してくれるので、そのままsort -uできます。 - ワイルドカード証明書(
*.example.com)は個別ホスト名を教えてくれません。CTログだけに依存しないこと。 - 自己署名証明書やプライベートCAの資産はここに載りません。**CTログは「万能」ではなく「取りこぼしの少ない初手」**です。
- 内部向けのつもりのホスト名がここに載っていたら、それはすでに公開情報です。「名前を知られていないから安全」という前提の設計は、この時点で成立していません。
2-2. 生きているホストを絞り込む
while read -r h; do
a=$(dig +short "$h" A | tr '
' ',')
c=$(dig +short "$h" CNAME | tr '
' ',')
if [ -n "${a}${c}" ]; then
printf '%s\t%s\t%s
' "$h" "$a" "$c"
fi
done < ct_hosts.txt | tee live_hosts.tsv
Windowsだけで完結させたい場合はPowerShellでも同じことができます。
Get-Content .\ct_hosts.txt | ForEach-Object {
$r = Resolve-DnsName $_ -Type A -ErrorAction SilentlyContinue
if ($r) {
[pscustomobject]@{ Host = $_; IP = ($r.IPAddress -join ',') }
}
} | Export-Csv .\live_hosts.csv -NoTypeInformation -Encoding UTF8
2-3. そのIPは本当に自社のものか(RDAP)
# 割り当て元レジストリに問い合わせる(APNIC / JPNIC 管理の例)
curl -s https://rdap.apnic.net/ip/203.0.113.10 | jq -r '.name, .handle, .country'
# ドメイン側の登録情報
curl -s -H 'Accept: application/rdap+json' https://rdap.verisign.com/com/v1/domain/example.com |
jq -r '.ldhName, (.events[] | "\(.eventAction) \(.eventDate)")'
自社割り当てではないIPに向いているホスト名は、外部委託先やSaaS上で動いているということです。ここで判断基準を間違えないでください。棚卸しの範囲は「誰の環境か」ではなく、**「誰の看板か」**で決めます。自社ドメインで公開されている以上、事故が起きれば自社の名前で報道されます。
2-4. サブドメインテイクオーバーを確認する
CNAMEが外部サービスを指しているのに、そのサービス側のリソースが解約・削除されていると、第三者が同じ名前でリソースを再取得してホスト名を乗っ取れることがあります。退職・キャンペーン終了の後に残りやすい典型パターンです。
H=old-campaign.example.com
dig +short "$H" CNAME
curl -sI "https://${H}" | head -n 1
curl -s "https://${H}" | head -c 300
サービス固有の「そんなサイトはありません」系のエラー本文が返ってきたら要調査です。対処はそのCNAMEレコードを消すこと。使っていないDNSレコードを残さないのが唯一の恒久対策です。
2-5. 開いているポートを確認する(自社IPのみ)
# まず全ポートを浅く(対象は自社割り当てレンジのみ)
sudo nmap -Pn -sS -p- --min-rate 1000 -oA asm_$(date +%F) 203.0.113.0/28
# 反応したポートだけサービス/バージョンを取る
sudo nmap -sV -p 22,443,3389,8443,10000 203.0.113.0/28
見るべきは「Webが開いているか」ではなく、管理系の口が外に出ていないかです。SSH(22)、RDP(3389)、装置の管理UI(8443 / 10000 など)、DBポート(3306 / 5432 / 1433)あたりが外部から応答したら、原則そこは閉じる方向で検討します。
2-6. 何が動いているかの手掛かり
curl -sI https://www.example.com | grep -Ei 'server|x-powered-by|x-generator|x-drupal|x-aspnet'
CMSやミドルのバージョンがヘッダに出ているなら、それは攻撃者にも見えています。バージョン露出を消すのは対症療法ですが、「そもそもEOLの版が動いていないか」の発見には役立ちます。
2-7. メール関連の残骸
dig +short example.com MX
dig +short example.com TXT | grep -i 'v=spf1'
dig +short _dmarc.example.com TXT
dig +short selector1._domainkey.example.com TXT
過去に使っていた配信サービスが include: に残っているケースが多いです。使っていないものは削除します。放置はなりすまし送信の余地を残すうえ、SPFのDNSルックアップ上限(10回)にも効いてきます。
2-8. 証明書の期限と発行対象
echo | openssl s_client -connect www.example.com:443 -servername www.example.com 2>/dev/null |
openssl x509 -noout -subject -issuer -dates -ext subjectAltName
SANに、もう存在しないはずのホスト名が入っていることがあります。
2-9. (任意)OSSツールで効率化
件数が多いなら、ProjectDiscovery系などのOSSでパイプラインにできます。自社資産限定という前提は同じです。
subfinder -d example.com -silent |
dnsx -silent -a -resp |
httpx -silent -status-code -title -tech-detect -o httpx_result.txt
ただし、ツールを増やす前にやることは次のStep2です。検知結果を受け取る人が決まっていないツールは、通知が溜まるだけの仕組みになります。
3. Step2: 台帳に「持ち主」を書く
棚卸しの成果は資産の件数ではありません。目的は所有者不明の資産をゼロに近づけることです。管理指標もそこに置くと、運用が形骸化しにくくなります。
CSV(表計算1枚)で十分です。列の例:
asset_id,種別,識別子,用途,公開の必要性,所管部署,担当者,契約先,停止判断者,期限(EOL/保守/契約),最終確認日,状態
A-001,ドメイン,example.co.jp,コーポレートサイト,必要,経営企画,山田,レジストラA,情シス課長,2027-03-31,2026-07-28,運用中
A-014,VPN装置,203.0.113.20,拠点間接続,必要,情シス,佐藤,SIerB,情シス課長,2026-12-31(保守),2026-07-28,FW更新待ち
A-032,検証サーバ,stg.example.com,3年前の検証,不要,不明,不明,不明,不明,,2026-07-28,廃止候補
A-047,SaaS,xxxx.service.example,部門契約の名刺管理,要確認,営業部,不明,不明,不明,,2026-07-28,シャドーIT疑い
運用ルールはシンプルに2つだけ。
- 「不明」が入っている行が、常に最優先の調査対象
- 公開の必要性が「不要」の行には、廃止判断の期限日を入れる
ここを飛ばすと、後の工程がすべて止まります。逆に言えば、脆弱性スキャナを買うより先にこの列を埋めるほうが効きます。
4. Step3: 危険度で並べる(CVSSの数値だけで並べない)
全部を同時に直そうとすると、結局どれも進みません。優先軸は次の3つです。
- 実際に悪用が確認されているか
- インターネットから認証なしで触れるか
- 重要なデータを持つ/中継するか
1については、CISAのKEV(Known Exploited Vulnerabilities)カタログが機械可読で公開されています。自社が持っている境界機器のベンダーで絞り込むだけでも、優先順位はかなりはっきりします。
curl -s https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json |
jq -r '.vulnerabilities[]
| select(.vendorProject | test("Fortinet|Ivanti|Citrix|Cisco|Progress"; "i"))
| [.cveID, .vendorProject, .product, .dateAdded] | @tsv' |
sort -k4 -r | head -n 20
悪用可能性のスコアを見たい場合はEPSSのAPIも使えます。
curl -s "https://api.first.org/data/v1/epss?cve=CVE-2024-3400" | jq '.data'
国内の情報源としては JVN iPedia / JPCERT-CC の注意喚起も併せて購読しておくと、日本語で拾えます。
5. Step4: 直す前に、まず「閉じる」
検討の順番を間違えないことが重要です。
- 廃止する — 使っていないなら止める。最も確実で、最も費用のかからない対策。
- 露出を減らす — 管理画面はインターネットに出さず、社内網かZTNA経由に限定する。オリジンをプロキシ/CDNの背後に置く。
- 残したものだけを、更新と監視の対象にする。
例: CDN/プロキシを使っているのにオリジンIPが素通しになっているケース
WAFを入れても、オリジンのグローバルIPが直接叩ける状態なら簡単に迂回されます。オリジン側でプロキシ事業者のIPレンジ以外を落とします。
# 事業者が公開しているIPレンジを取得して反映する(例: Cloudflare)
curl -s https://www.cloudflare.com/ips-v4 -o cf-ips-v4.txt
curl -s https://www.cloudflare.com/ips-v6 -o cf-ips-v6.txt
sudo ufw default deny incoming
while read -r cidr; do
[ -n "$cidr" ] && sudo ufw allow from "$cidr" to any port 443 proto tcp
done < cf-ips-v4.txt
sudo ufw reload
注意: IPレンジは事業者側で更新されます。手で1回入れて終わりにせず、定期取得して差分を反映する運用にしてください。ここを放置すると、ある日突然サイトが落ちます。
6. Step5: 差分を監視する(ここまで来て初めてASM)
1回の棚卸しは翌週には古くなります。月1回でよいので「先月から増えた資産・消えた資産」を出す定例を作ります。
#!/usr/bin/env bash
set -euo pipefail
DOMAIN="example.com"
DIR="${HOME}/asm"
mkdir -p "$DIR"
TODAY="${DIR}/hosts_$(date +%F).txt"
LATEST="${DIR}/hosts_latest.txt"
curl -s --retry 3 --retry-delay 5 "https://crt.sh/?q=%25.${DOMAIN}&output=json" |
jq -r '.[].name_value' |
sed 's/^\*\.//' |
tr 'A-Z' 'a-z' |
sort -u > "$TODAY"
# 取得失敗時に空ファイルで台帳を上書きしないためのガード(必須)
if [ ! -s "$TODAY" ]; then
echo "取得に失敗しました: $TODAY が空です" >&2
exit 1
fi
if [ -f "$LATEST" ]; then
ADDED=$(comm -13 "$LATEST" "$TODAY" || true)
REMOVED=$(comm -23 "$LATEST" "$TODAY" || true)
if [ -n "$ADDED" ]; then
printf '[追加されたホスト]
%s
' "$ADDED"
fi
if [ -n "$REMOVED" ]; then
printf '[消滅したホスト]
%s
' "$REMOVED"
fi
fi
cp "$TODAY" "$LATEST"
このスクリプトのつまずきどころ
-
空ファイルガードは必ず入れる。 crt.shが落ちていた日に空の結果で
hosts_latest.txtを上書きすると、翌月に全ホストが「新規追加」として爆発します。 -
set -e環境で[ -n "$X" ] && printf ...と書くと、$Xが空のときにAND списокの終了ステータスが1になりスクリプトごと終了します。上記のようにif文で書くこと。 -
commは両方の入力がソート済みであることが前提です。sort -uを外さないこと。 - 差分結果はメールやチャットに飛ばして、必ず人が見る導線を作ります。ログファイルに書くだけだと誰も読みません。
定期実行の登録
cron(毎月1日 9:00):
0 9 1 * * /home/opsuser/asm/ct_diff.sh >> /var/log/asm_diff.log 2>&1
WindowsのタスクスケジューラからWSL経由で回す場合:
$action = New-ScheduledTaskAction -Execute 'C:\Windows\System32\wsl.exe' -Argument '-e /home/opsuser/asm/ct_diff.sh'
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At 9:00
Register-ScheduledTask -TaskName 'ASM-CT-Diff' -Action $action -Trigger $trigger -RunLevel Highest
Windowsのタスクスケジューラでスクリプトを回す場合、実行ファイルはフルパス指定にしてください。パスが解決できず
0x80070002で落ちるのはよくある事故です。
7. 何を「外部公開資産」として数えるか(チェックリスト)
棚卸しの精度は、この網羅性で決まります。
-
ドメインとサブドメイン — 本体サイトのほか、キャンペーン用・採用用・旧サービス用、
devstgtest、外部サービス向けCNAME - グローバルIPと公開ポート — 自社割り当てレンジで、どのポートが外部から応答するか
- 境界に置いた通信機器 — VPN / FW / ロードバランサ / リモートアクセス製品。機種・ファーム版数・保守期限・EOL予定まで記録
- クラウド上のリソース — VM、公開設定のオブジェクトストレージ、外部公開されたDB、検証目的で作ったまま残っている環境
- SaaS・外部サービスのテナント — 部門が独自契約したものを含む。誰がアカウントを持ち、退職時に誰が止めるかまで
- サイトを構成するソフトウェア — CMS本体・プラグイン・テーマ・ライブラリの版数、外部から読み込む第三者スクリプト
- メール関連 — MX、SPF / DKIM / DMARC、使っていない送信サービスの残骸
- 証明書 — 有効期限と発行対象
- 公開されたコードや文書 — 外部のコード共有サービスや公開フォルダに、設定ファイルや接続情報が残っていないか
8. よくある誤解とハマりどころ
- 「ASMツールを入れれば解決」ではない — ツールは見つけて知らせるところまで。持ち主を決め、止める判断をするのは人です。誰が通知を見て誰が対応するかを決めないまま導入すると、アラートが溜まるだけになります。
- 調査は自社の資産に対してのみ — 共用ホスティングやクラウドは、事業者の利用規約で許可される範囲を必ず確認。
- グループ会社・委託先も攻撃面 — 自社の名前で公開されているサイトが委託先の環境で動いているのは珍しくありません。範囲は「誰の看板か」で決めます。
- 「止める」判断を先送りしない — 「念のため残す」が積み重なって攻撃面は広がります。廃止可否の判断期限を決めて運用します。
- 成果は件数ではない — 数えることが目的ではなく、所有者不明をゼロに近づけることが目的。
- CTログを万能視しない — ワイルドカード証明書や非公開CAの資産は載りません。DNS・請求書(契約中のドメイン/IP/SaaS)・クラウドの請求明細と突き合わせると漏れが減ります。「支払っているもの」から資産を逆引きするのは、意外と強い手です。
まとめ
- 診断は「深さ」、ASMは「広さ」。役割が違う道具で、片方では代替できない。
- 初手は無料で足りる。CTログ → DNS → RDAP → 台帳との差分、ここまでは今日できる。
- 台帳で一番大事な列は資産名ではなく、持ち主と停止判断者。
- 直す前に閉じる。廃止と露出削減が、最も確実で最も安い。
- 月1回の差分検知まで到達して、初めて「棚卸し」が「管理」になる。
攻撃面は「守る」前に「減らす」。限られた人員でセキュリティを維持するうえで、この順序が一番効きます。
株式会社ブレインディレクションは、ITソリューション・生成AI・クラウド・セキュリティを手がけ、ISMS(ISO/IEC 27001)を取得しています ― https://www.brain-d.jp