Wazuh REST APIでCVEリストを取得し、Claude Codeに渡したら調査と修正コマンド生成が自動化できた。93件のCriticalを手動で追っていた状態からの脱出実録。
この記事で分かること
- WazuhのREST APIでCritical CVEリストをJSONで取得する手順
- JSONをClaude Codeに渡して「即対処 / 無視可」を自動分類させるプロンプト設計
- cronで毎週自動実行する仕組みの作り方
- Wazuh 4.14.x以降のAPIエンドポイント廃止への対処法(OpenSearch直接クエリ)
WazuhのREST APIにはJWT認証(JSON Web Token: リクエストごとに発行される短命の認証トークン)が必要だ。まずトークンを取る。
# JWTトークン取得(RasPi5でWazuh Manager稼働中)
TOKEN=$(curl -s -X POST \
"https://localhost:55000/security/user/authenticate?raw=true" \
-u admin:PASSWORD -k)
echo $TOKEN
-k は自己署名証明書を許可するオプション。自宅ラボなので割り切っている。このトークンの有効期限は900秒(15分)。「15分で切れるの不便じゃないか」と思う人もいると思うが、スクリプト内で都度取得すれば問題ない。後述する。
うちのWazuh構成は
BlogRasPi 5にWazuhを入れたら、Dockerが死んだ話——自宅セキュリティ監視の実録RasPi 5(ARM64)にWazuhをDocker Composeで構築しようとしたらIndexerが落ちて失敗。直接インストールに切り替えた実録。3台を監視したらCritical脆弱性が203件出てきた話。→に書いた通り、RasPi5をManagerとして、WorkStation(008)・AIServer(009)・Win11(010)の3台をAgentにしている。
# エージェント一覧確認
curl -s -X GET "https://localhost:55000/agents" \
-H "Authorization: Bearer $TOKEN" -k \
| jq '.data.affected_items[] | {id, name, ip}'
# エージェント008(WorkStation)のCritical脆弱性を取得
curl -s -X GET \
"https://localhost:55000/vulnerability/008?severity=Critical&limit=100&offset=0" \
-H "Authorization: Bearer $TOKEN" -k | jq .
severity=Critical で絞ることで、High以下のノイズを排除できる。limit は最大500まで指定可能。
⚠️ Wazuh 4.14.x 以降の注意
/vulnerability/{agent_id}エンドポイントは 4.14.x で廃止された。 代わりに OpenSearch インデックスへ直接クエリする方法を使う(後述)。
APIが返すJSONはこういう構造になっている。
// wazuh-states-vulnerabilities-raspi5 から取得(上位5件、CVSSスコア降順)
{
"data": {
"affected_items": [
{
"cve": "CVE-2025-25467",
"name": "libavformat60",
"version": "7:6.1.1-3ubuntu5+esm7",
"severity": "Critical",
"cvss3_score": 9.8,
"title": "libx264 memory tracking vulnerability — arbitrary code execution via crafted AAC file"
},
{
"cve": "CVE-2023-3326",
"name": "libnss-sss",
"version": "2.9.4-1.1ubuntu6.3",
"severity": "Critical",
"cvss3_score": 9.8,
"title": "pam_krb5 KDC response validation bypass — authentication bypass for any user"
},
{
"cve": "CVE-2023-3326",
"name": "sssd-krb5",
"version": "2.9.4-1.1ubuntu6.3",
"severity": "Critical",
"cvss3_score": 9.8,
"title": "同上(sssd-krb5パッケージ)"
},
{
"cve": "CVE-2025-25467",
"name": "libx264-164",
"version": "2:0.164.3108+git31e19f9-1",
"severity": "Critical",
"cvss3_score": 9.8,
"title": "libx264 memory tracking vulnerability(本体)"
},
{
"cve": "CVE-2025-25467",
"name": "libavutil58",
"version": "7:6.1.1-3ubuntu5+esm7",
"severity": "Critical",
"cvss3_score": 9.8,
"title": "libx264 memory tracking vulnerability(libavutil58)"
}
],
"total_affected_items": 152
}
}
total_affected_items で件数を確認できる。今回は152件。condition フィールドに「どのバージョン未満が対象か」が書いてあるのが便利で、「対象バージョンを超えるパッケージをインストールすれば直る」という判断がそこで即座にできる。
参考: CVE-2025-25467(NVD) / CVE-2023-3326(NVD)
Wazuh 4.14.x では /vulnerability/{agent_id} が廃止されたため、OpenSearch(Wazuhが内部で使用する分散検索エンジン)のインデックスを直接クエリする必要がある。インデックス名はホスト名を含む形式(例: wazuh-states-vulnerabilities-raspi5)。
curl -s -X GET 'https://localhost:9200/wazuh-states-vulnerabilities-raspi5/_search' \
-u admin:PASSWORD -k \
-H 'Content-Type: application/json' \
-d '{
"size": 5,
"query": {"term": {"vulnerability.severity": "Critical"}},
"sort": [{"vulnerability.score.base": {"order": "desc"}}],
"_source": ["vulnerability.id", "package.name", "package.version",
"vulnerability.score.base", "vulnerability.description"]
}'
9200 は OpenSearch のデフォルトポート。接続には Wazuh と同じ admin 認証情報を使う。
参考: OpenSearch Query DSL(公式ドキュメント)
fetch-wazuh-cves.sh スクリプト毎回手打ちするのは面倒なので、CVEリスト取得を1コマンドにまとめた。
#!/bin/bash
# fetch-wazuh-cves.sh
# WazuhからCritical CVEリストを取得してJSONファイルに保存
set -e
WAZUH_HOST="localhost:55000"
WAZUH_USER="admin"
AGENT_ID="${1:-008}"
OUTPUT_FILE="cve-report-$(date +%Y%m%d).json"
# JWT取得(都度取得でトークン切れを防ぐ)
TOKEN=$(curl -s -X POST \
"https://${WAZUH_HOST}/security/user/authenticate?raw=true" \
-u "${WAZUH_USER}:${WAZUH_PASS}" -k)
# Critical CVE取得
curl -s -X GET \
"https://${WAZUH_HOST}/vulnerability/${AGENT_ID}?severity=Critical&limit=500" \
-H "Authorization: Bearer $TOKEN" -k \
| jq '.data.affected_items' > "$OUTPUT_FILE"
echo "保存完了: $OUTPUT_FILE ($(jq length "$OUTPUT_FILE")件)"
WAZUH_PASS はスクリプトにハードコードしていない。WAZUH_PASS=xxx bash fetch-wazuh-cves.sh 008 のようにシェル変数として渡す。パスワードをコード中に書くのは20年エンジニアとして体が拒否する。
JSONを渡す前に、プロンプトの設計が肝心だ。「分析して」だけ渡すと、Claude Codeは律儀にすべてのCVEを丁寧に解説し始める。それをやられると逆に読む気がなくなる。
自分が欲しかったのは「今週これだけ対処すれば十分」というリスト。そのためのプロンプトがこれだ。
これはWazuh(SIEM: Security Information and Event Management)が検出したCritical CVEリストです。
以下の観点で優先度分類と対処コマンドを生成してください。
優先基準:
1. 即対処: CVSS(脆弱性深刻度スコア)>= 9.0 かつ攻撃経路がネットワーク(AV:N)
2. 待機: パッチ未リリース または影響限定的
3. 無視可: 当該サービスを使っていない(例: Kerberos, SNMP等)
出力形式:
- 優先度別の表(CVE ID / パッケージ / 理由 / 対処コマンド)
- 即対処分の apt コマンド(コピペ可能な形式)
- 「無視可」判断の根拠を明示すること
[JSONデータをここに貼り付け]
「無視可」の根拠明示がポイントだ。以前は「Kerberosのパッケージに脆弱性がある」と書いてあるのを見て「Kerberos……うちのWSSで動いてたっけ?」と10分悩んでいた。Kerberos認証なんて使っていないのに。Claude Codeに「使っていないサービス名を列挙して無視可と判断せよ」と指示すると、その根拠が明示されて判断の迷いが消えた。
実際に上記JSONを渡したところ、以下の出力が返ってきた。
| 優先度 | CVE | パッケージ | 判断理由 | 対処 |
|---|---|---|---|---|
| 即対処 | CVE-2025-25467 | libx264-164, libavformat60, libavutil58 | CVSS 9.8・細工されたメディアファイルで任意コード実行・ffmpeg経由で悪用可能性あり | sudo apt upgrade libx264-164 libavformat60 libavutil58 |
| 無視可 | CVE-2023-3326 | libnss-sss, sssd-krb5 | Kerberos/SSSD認証未使用・RasPi5はローカル認証のみ・ネットワーク経由の到達経路なし | 対処不要 |
即対処コマンド(コピペ可能):
sudo apt update && sudo apt upgrade libx264-164 libavformat60 libavutil58
表の下に「即対処分のaptコマンド一覧」がまとめて出力される。それをコピーして実行するだけ。「判断を人間がして、コマンド生成をAIに任せる」——この分担が自分には合っている。
CVE-2023-3326 の「無視可」判断は的確だった。Kerberos認証は使っておらず、SSSD(System Security Services Daemon)もローカルユーザー管理だけで使っているケースでは、ネットワーク越しの悪用経路がない。Claude Codeはこの根拠を自動で明示してくれた。
週1でこれを手動で叩くのもいいが、どうせならcronで自動実行してしまう。
#!/bin/bash
# check-cves-with-claude.sh
# 使い方: WAZUH_PASS=xxx bash check-cves-with-claude.sh [agent_id]
set -e
AGENT_ID="${1:-008}"
DATE=$(date +%Y%m%d)
JSON_FILE="/tmp/cve-${DATE}.json"
PROMPT_FILE="/tmp/cve-prompt-${DATE}.txt"
# 1. CVEリスト取得
bash ~/scripts/fetch-wazuh-cves.sh "$AGENT_ID"
mv "cve-report-${DATE}.json" "$JSON_FILE"
# 2. プロンプト構築
cat > "$PROMPT_FILE" <<EOF
$(cat ~/.wazuh/cve-analysis-prompt.txt)
$(cat "$JSON_FILE")
EOF
# 3. Claude Codeで分析
claude --print < "$PROMPT_FILE"
crontab登録例:
# 毎週月曜 9:00 にCVEレポートを生成
0 9 * * 1 WAZUH_PASS=xxx bash ~/scripts/check-cves-with-claude.sh 008 > ~/logs/cve-report.log 2>&1
一点、注意。WAZUH_PASS=xxx をcrontabに直書きするのは、crontab -l の出力がログに残るケースで問題になる。より安全な方法は .env ファイルを作って source する方式だ。
# ~/.wazuh/.env
WAZUH_PASS=xxx
# crontab
0 9 * * 1 source ~/.wazuh/.env && bash ~/scripts/check-cves-with-claude.sh 008 > ~/logs/cve-report.log 2>&1
.env ファイルは chmod 600 で自分だけ読めるようにしておく。これくらいの対策は最低限やっておく。
ちなみに SSH 鍵のほうは
Blogパスワードも SSH 鍵も、自宅に置いておく——Vaultwarden + Bitwarden SSH Agent で作る個人セキュリティ基盤クラウドパスワードマネージャーへの不安と ~/.ssh/ ファイル管理の煩雑さを、Vaultwarden(RasPi 5 Docker)と Bitwarden SSH Agent の組み合わせで解決した実録。GNOME Keyring の競合解消・毎回確認ダイアログによるプロンプトインジェクション対策・snap バージョン非依存のソケット設定まで解説。→で保管庫管理に移していて、~/.ssh/ に秘密鍵ファイルを置くのをやめた。秘密の置き場所を減らすという意味では同じ話だ。
この方法に切り替えて3週間ほど経つ。実際にどう変わったかを正直に書く。
| 指標 | Before(手動) | After(Claude Code連携) |
|---|---|---|
| 週あたり調査時間 | 20〜30分 | 3〜5分 |
| CVE 1件あたりの作業 | NVDを開いて手動検索 | 自動分類+コマンド生成 |
| 見落としリスク | 高(疲れると雑になる) | 低(全件スキャン) |
| 「無視可」の根拠 | 曖昧(なんとなく) | 明示(使っていないサービス名) |
| 新しいCVEの検出 | 次にWazuhを開いたとき | cronで自動検出 |
数字は正確ではないが、体感はこれに近い。特に「無視可の根拠が明示される」のは想定以上に価値があった。「なんとなく対処しなかった」から「理由があって対処しなかった」に変わる。後から見返したときの安心感が違う。
3週間で手動確認は2回しかしていない。残り10回はレポートを一読してaptコマンドを実行するだけで済んだ。自分にとって「続けられる運用」とはこういうものだと思っている。
デメリットも書く。CVEのJSONにはホストのパッケージ名・バージョンが含まれる。それをClaudeに送ることへの懸念は理解できる。自分は「CVEはすべて公開情報で、パッケージ名は攻撃者にとって大して有益ではない」と判断したが、これはユーザー自身が決めることだ。claude.ai/cloudへの送信になる点は把握した上で使っている。
この自動化の土台になっている監視環境そのもの(RasPi 5×Wazuhの4.5ヶ月運用実測)は
BlogLinux向け無料EDRは実質Wazuh一択だった — 自宅RasPi 5で4.5ヶ月運用した本音と実測個人のLinuxで無料EDR的監視はWazuhが現実的。RasPi 5で4台を4.5ヶ月運用した実測と、商用EDRとの違いを整理します。→ で全体像を書いています。
BlogWazuhを入れたら脆弱性が3,247件検出された話【RasPi 5 自宅ラボ セキュリティ実録 #2】Wazuhの脆弱性ダッシュボードに「3,247件」と表示された。焦ったが、分解してみると数字の正体はシンプルだった。旧カーネル・謎のThunderbird・CVSS 9.8の正体——自宅ラボ実録。→で「管理できている状態を維持することが目的」と書いた。その言葉は今も変わっていない。
ただ、「管理できている状態」のコストを下げることはできた。週30分が週5分になれば、続けられる確率が上がる。セキュリティ対策が続かない最大の理由は「面倒になること」だと思っている。
Claude Codeは調査を加速してくれた。判断は自分でした。「このCVE、対処すべきか」は最終的に自分が決めた。それでいいと思っている。AIに判断まで投げると、なぜそうなったのかがわからなくなる。
次は、SlackへのアラートBot連携と、AIServer(009)・Win11(010)を同様のフローに乗せることを考えている。Win11は別途Agentの挙動確認が必要になるが、それはまた別の話だ。
現時点では raspi5(RasPi5/Manager 兼 Agent)のみ脆弱性インデックスが生成されている。WorkStation(008)・AIServer(009)・Win11(010)は Agent 設定完了後、脆弱性スキャンが走るまでインデックスが生成されない。これも順次対応予定だ。
Q. Wazuh APIのJWT認証、15分で切れるのは不便では?
スクリプト内で TOKEN=$(...) を毎回実行すれば問題ない。自分のスクリプトはすべてこの形にしている。関数化すると見通しがよくなる。
Q. CVEの詳細をClaude Codeに渡して情報漏洩のリスクは?
CVEはすべて公開情報だ。含まれるのは自ホストのパッケージ名とバージョンだが、それ自体が直接のリスクかどうかはユーザーが判断する話。「claude.ai/cloudに送信される」という点は認識した上で使うべきで、気になる人はオンプレミス環境のClaude APIを使う選択肢もある。
Q. VPSやクラウド環境でも同じ方法は使える?
Wazuh AgentはほぼすべてのLinuxディストリビューションに対応している。Managerが別ホストにある場合は、APIのアクセス先を変えるだけだ。クラウド環境ではManagerへのAPIアクセスにセキュリティグループやファイアウォール設定が別途必要になる。自宅ラボより設定の手間は増えるが、仕組みは同じ。
HW系エンジニアとして20年以上、10,000件を超える顧客訪問と2,000件を超える単独ソリューション実績。AIツールを使った個人開発やIoT農園など、Raspberry Piを使ったオートメーション化なども実践中です!エンジニア専門結婚相談所も運営中、ClaudeCodeで解決できない心の課題も解決いたします!
この記事のダイジェスト版(構成コードとcronスクリプト中心)を Zennにも投稿しています。
META-MARK × AI
ローカルAIを動かすGPU、ちゃんと選べていますか?
VRAM・性能・コスパをMetaScoreで数値化。AIアプリ別の推奨ハードウェア要件も確認できます。