はじめに
2026年10月21日、PHP と Node.js でリモートコード実行(RCE)の緊急パッチが出ました。
大きな騒ぎになりましたが、これを受けて自分の AWS 環境の状態も気になったので、本記事では個人アカウント内の脆弱性管理に目を向けてみます。
最近、Kiro Crew をプライベートサブネットに置いた EC2 に立てて運用しています(詳細はこちら)。インターネットに素で公開しない設計にしたので、正直どこかで「VPC 内だから大丈夫だろう」と思っていました。
今回の CVE で自分の環境が直接やられたわけではありませんが、昨今のドラスティックな世の動きを見ていると他人事とは思えません。そこでこれを機に、自分の環境を点検してセキュリティ対策を講じてみることにします。
なお、キッカケとなった脆弱性の具体的な内容や安全なアップデート手順は、@superdora-cloud さんの以下の解説記事が参考になりました。
今回の環境
点検対象は、Kiro Crew 本体が乗っている EC2 と、その通信経路になっている NAT インスタンスの2台です。AWS アーキテクチャ図は、このようになっています。
| 部品 | 採用したもの | 狙い |
|---|---|---|
| Crew EC2(本体) | プライベートサブネット・Public IP なし | インバウンドの口を作らない |
| NAT インスタンス | EC2(Public サブネット・t4g.nano) | アウトバウンド経路を安く確保する |
| 管理経路 | SSM セッションマネージャー | SSH の口を開けない |
| インバウンド | CloudFront VPC origins + Lambda@Edge の Cognito | 認証を通らないと EC2 に届かない |
つまり「入口を作らない」ことに振り切った構成です。ただ、入口を絞ったこととは別に、OS やライブラリに溜まる CVE は公開・非公開と無関係に積み上がっていきます。今回扱うのは、この「EC2 の中に溜まる既知の脆弱性」を検知してパッチを当てる仕組みです。
対象インスタンスと現状把握
今回手を入れる対象は、Kiro Crew 用に立てた EC2 2台です。Kiro Crew 本体と、アウトバウンド用の NAT インスタンスです。
| 役割 | タイプ | OS | サブネット / パブリック IP |
|---|---|---|---|
| Kiro Crew 本体 | r6a.large | Amazon Linux 2023 | Private / なし |
| NAT インスタンス | t4g.nano | Amazon Linux 2023 | Public / あり(外に出る口なので) |
NAT は本体の通信経路なので、パッチ適用のために本体を起こすときは一緒に起こしておきます。
EC2 で脆弱性管理の仕組みを整えるためには、インスタンスが SSM の管理下(Online)にあることが前提です。本構成構築時にセッションマネージャーを通しているので、ここはそのまま使えます。
# 2台とも SSM 管理下(Online)か確認
aws ssm describe-instance-information --region ap-northeast-1 \
--query "InstanceInformationList[].{id:InstanceId,ping:PingStatus,agent:AgentVersion,latest:IsLatestVersion}"
2台とも PingStatus: Online で管理下にありました。1つだけ、IsLatestVersion: false(SSM Agent が最新ではない)だったので、これは Inspector を入れる前に更新しておきます。
その SSM 管理の土台の上に、脆弱性管理のための2つ、脆弱性の検知(Amazon Inspector)とパッチの適用(Patch Manager)を足していきます。
前提となるサービス
設定に入る前に、本記事で主役になる2つのサービスを簡単に押さえておきます。どちらも「EC2 を安全に保つ」ための AWS サービスです。
AWS Systems Manager(SSM)とは
AWS Systems Manager(SSM) は、EC2 などのサーバを一元管理するためのサービス群です。今回の点検に関係するのは、主に次の機能です。
- セッションマネージャー … SSH の口(22番ポート)も踏み台サーバも開けずに、ブラウザや CLI から EC2 のシェルに入れる仕組み。この環境では、Public IP を持たないプライベートな EC2 に入る唯一の経路になっています。ポイントは、これが「外から EC2 のポートを開けて入る」のではなく、EC2 側の SSM Agent が SSM サービスへ外向き(アウトバウンド)に張った接続に相乗りする方式だということです。だからインバウンドの穴を一切開けず、アウトバウンド専用の NAT 経由だけで接続が成立します
- Patch Manager … OS やミドルウェアのパッチ適用を一元管理する機能。「どのパッチを承認するか」を決めるパッチベースライン、対象をまとめるパッチグループ、いつ当てるかを決めるメンテナンスウィンドウを組み合わせて、スキャン(未適用の洗い出し)と適用(インストール)を回します
SSM は各 EC2 の中で動く SSM Agent(常駐プログラム)を通じて命令を届けます。Agent が動いてサーバが「管理下(Online)」になっていることが、あらゆる SSM 機能の前提です。なお Amazon Linux 2 / 2023 の AMI には SSM Agent が最初から入っているので、今回のように Amazon Linux 系なら Agent のインストール自体は不要です。
Amazon Inspector とは
Amazon Inspector は、EC2 / ECR(コンテナイメージ)/ Lambda をスキャンして、既知の脆弱性(CVE)の影響を受けるソフトウェアが入っていないかを自動で継続的に突き合わせるサービスです。
- インスタンスにインストールされているパッケージの一覧(目録)を集め、公開されている CVE データベースと照合します
- 新しい CVE が公開されたとき、「自分のサーバはこれに該当するか」を自動で判定し、該当すれば finding(検出結果)として深刻度(CRITICAL / HIGH など)付きで挙げてくれます
- EC2 のスキャンは、先ほどの SSM Agent 経由で動きます(つまり Inspector も SSM 管理下であることが前提です)
ざっくり言うと、Inspector が「危ない箇所を見つける(検知)」側、Patch Manager が「パッチを当てて直す(適用)」側です。今回の点検は、この2つがちゃんと動いているかを見ていきます。
なお Inspector の EC2 スキャンも SSM Agent 経由なので、SSM 管理下にあることが前提です。両者は直接連携しているわけではなく、Patch Manager がパッケージを新しくした結果を Inspector が再スキャンで拾うことで、検知していた CVE が finding から消えていきます。
検知と適用の設計
PHP2Shell のような「ランタイム・ライブラリの CVE」に対して、自分の EC2 に「検知」と「適用」の2本柱を入れていきます。検知が Amazon Inspector、適用が Patch Manager です。
設計の軸は3つ。
- パッチ適用を自動化すること
- リスクを検知したら通知を飛ばすこと
- 「固定費を抑える」方針を壊さないこと
ネットワークの観点では、既存の SSM Agent に乗せる事ができるので、構成変更は発生しません。
パッチ適用を自動化する
マニュアルでのパッチ適用はすぐに忘れる自信があるので、Patch Manager のメンテナンスウィンドウ(パッチを当てる時間帯を決めるスケジュール機能)で定期実行の仕組みを構築します。
- インスタンスに Patch group タグを付けて、パッチベースラインに紐づける
- メンテナンスウィンドウを作る(スケジュールを決める)
- タスクとして
AWS-RunPatchBaselineを登録する(Operation で Scan か Install かを選ぶ)
今回は「見つかった CVE を確実に塞ぐ」ことが目的なので、Scan だけで止めず Scan + Install(実際に当てる) を自動で回す方針にしました。
まず、対象インスタンスにパッチグループのタグを付けます。これで Amazon Linux 2023 用の既定ベースライン(AWS-AmazonLinux2023DefaultPatchBaseline)が自動で適用されます。
# パッチグループのタグを付ける(キーは Patch-Group に統一)
aws ec2 create-tags --region ap-northeast-1 \
--resources <INSTANCE_ID> <NAT_INSTANCE_ID> \
--tags "Key=Patch-Group,Value=kiro-crew"
タグキーは任意ですが、この記事では Patch-Group に統一しています。EC2 側のタグと、次のターゲット登録の Key=tag:... を必ず一致させてください。異なる値を設定するとターゲットが1台も解決されず空振りします。
ここで今回の環境ならではの問題に気付きました。
「夜は止める運用」とパッチの自動適用は両立しない
今回の環境では「使うときだけ起動し、終わったら停止する」省コスト運用にしてあります。ここにメンテナンスウィンドウを足すと、思わぬ落とし穴があります。
Patch Manager の Scan も Install も、実体は AWS-RunPatchBaseline という処理をインスタンスの中(SSM Agent)で実行させる仕組みです。そしてメンテナンスウィンドウは、タグでターゲットを解決する時点で停止中のインスタンスを対象として拾いません(実行ログには "No Invocations to Execute" と出ます)。
つまり、夜に EC2 を落とす運用を徹底していると、その時刻のパッチは Scan も Install もされずにスキップされ続けます。タイミングが合わないとパッチは永遠に当たりません。
起動 → パッチ → 停止を1つのウィンドウに積む
解決策は、パッチのために自動で起こして・当てて・また落とすことです。メンテナンスウィンドウはタスクを優先度順に並べられるので、1つのウィンドウに3タスクを仕込みます。
| 優先度 | タスク | ドキュメント | 役割 |
|---|---|---|---|
| 1 | 起動 | KiroCrew-StartAndWaitOnline |
停止中でも起こし、SSM 管理下(Online)になるまで待つ(自作) |
| 2 | パッチ | AWS-RunPatchBaseline |
Scan + Install(RebootIfNeeded) |
| 3 | 停止 | AWS-StopEC2Instance |
起こした EC2 を停止する |
起動・停止の Automation(AWS-StartEC2Instance / AWS-StopEC2Instance)は、インスタンスの中の SSM Agent に命令を送るのではなく、外から EC2 の API(起動・停止)を叩くだけです。だから相手が停止中でも、SSM 管理下でなくても実行できます。
優先度1に AWS 既定の AWS-StartEC2Instance をそのまま使わず、自作の KiroCrew-StartAndWaitOnline にしているのが今回のポイントです(起動直後の空振りを避けるため)。
スケジュールは 週1回・日曜 JST 3:00 にしました。週1回の自動適用は個人環境として十分な頻度です。まずは定期実行用のメンテナンスウィンドウを作成します。
# 日曜 JST 3:00 に週1回。schedule-timezone で JST を直接指定
aws ssm create-maintenance-window \
--name "kiro-crew-weekly-patch" \
--schedule "cron(0 3 ? * SUN *)" \
--schedule-timezone "Asia/Tokyo" \
--duration 3 --cutoff 1 \
--region ap-northeast-1
duration 3(3時間)/ cutoff 1(終了1時間前からは新タスクを始めない)と広めに取り、起動 → Agent 登録待ち → パッチ → 再起動 → 停止を余裕で収めます。返ってきた WindowId(mw-xxxx)を控えて、次はターゲットを登録します。
# Patch-Group タグでターゲットを登録
aws ssm register-target-with-maintenance-window \
--window-id <WINDOW_ID> \
--resource-type INSTANCE \
--targets "Key=tag:Patch-Group,Values=kiro-crew" \
--region ap-northeast-1
# → 返ってくる WindowTargetId を控える(このあと複数箇所で使う)
先ほど作成したグループタグを指定することで、今回環境の EC2 2台を対象とします。
そして、このウィンドウにタスクを優先度順で3つ積みますが、その前に、メンテナンスウィンドウがタスクを無人実行するためのサービスロール(kiro-crew-mw-role)を用意します。中身は次のとおりです。
| 区分 | 内容 | なぜ必要か |
|---|---|---|
| 信頼ポリシー |
ssm.amazonaws.com の sts:AssumeRole
|
メンテナンスウィンドウがこのロールを引き受けて実行するため |
| 管理ポリシー | AmazonSSMMaintenanceWindowRole |
SSM / RunCommand / Automation 実行の土台 |
| インライン |
ec2:StartInstances / ec2:StopInstances
|
起動・停止タスクの実体 |
| インライン |
ec2:DescribeInstanceStatus / ec2:DescribeInstances
|
起動・停止後に「desired state に達したか」を確認する。無いと起動タスクが 403 で失敗する |
| インライン | ssm:DescribeInstanceInformation |
後述のカスタム Automation が「SSM 管理下(Online)になったか」を待つのに使う |
| インライン |
iam:PassRole(自ロールに限定) |
カスタム Automation に実行ロールを渡す(AutomationAssumeRole)ために必要 |
作成したロールの ARN(<MW_ROLE_ARN>)を、次の各タスク登録の --service-role-arn に渡します。
ここからそれぞれのタスクを設定していきますが、起動タスクに既定の AWS-StartEC2Instance をそのまま使うとハマります。このドキュメントは EC2 が running になった時点で成功を返しますが、その直後はまだ SSM Agent が「管理下(Online)」として登録されていないことがあります。すると次のパッチタスクがタグでターゲットを解決した瞬間に「対象インスタンスなし」と判定され、No invocations to execute. で何も当たらずにスキップされてしまいます。
対策は、起動したあと SSM が Online になるまで待つ カスタム Automation を使うことです。aws:executeAwsApi で起動し、aws:waitForAwsResourceProperty で running と PingStatus=Online を順に待つだけの短いドキュメントを1本作り、優先度1に置きます。
# kiro-crew-start-and-wait-online.yaml(起動 → running → SSM Online を待つ)
schemaVersion: '0.3'
description: Start instances and wait until SSM Online.
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
InstanceId: { type: StringList }
AutomationAssumeRole: { type: String }
mainSteps:
- name: StartInstances
action: aws:executeAwsApi
inputs: { Service: ec2, Api: StartInstances, InstanceIds: '{{ InstanceId }}' }
- name: WaitForRunning
action: aws:waitForAwsResourceProperty
timeoutSeconds: 600
inputs:
Service: ec2
Api: DescribeInstances
InstanceIds: '{{ InstanceId }}'
PropertySelector: '$.Reservations[0].Instances[0].State.Name'
DesiredValues: [running]
- name: WaitForSsmOnline
action: aws:waitForAwsResourceProperty
timeoutSeconds: 600
inputs:
Service: ssm
Api: DescribeInstanceInformation
Filters: [{ Key: InstanceIds, Values: '{{ InstanceId }}' }]
PropertySelector: '$.InstanceInformationList[0].PingStatus'
DesiredValues: [Online]
# 上記を Automation ドキュメントとして登録
aws ssm create-document \
--name "KiroCrew-StartAndWaitOnline" \
--document-type Automation \
--document-format YAML \
--content file://kiro-crew-start-and-wait-online.yaml \
--region ap-northeast-1
筆者は最初これに気づかず、AWS-StartEC2Instance のまま組んで初回ウィンドウをまるごと空振りさせました(起動は成功するのにパッチが0件スキップ)。同じ轍を踏まないよう、起動タスクは次の Online 待ちバージョンにしておくのがおすすめです。
このドキュメントを含め、パッチ適用のための3つのタスクを登録していきます。
# 優先度1: 起動 + SSM Online 待ち(自作 Automation)
aws ssm register-task-with-maintenance-window \
--window-id <WINDOW_ID> \
--task-type AUTOMATION \
--task-arn "KiroCrew-StartAndWaitOnline" \
--service-role-arn <MW_ROLE_ARN> \
--priority 1 --name "start-and-wait-online" \
--task-invocation-parameters '{"Automation":{"Parameters":{"InstanceId":["<INSTANCE_ID>","<NAT_INSTANCE_ID>"],"AutomationAssumeRole":["<MW_ROLE_ARN>"]}}}' \
--region ap-northeast-1
# 優先度2: パッチ(Scan + Install / RebootIfNeeded)。ターゲットは先ほどの登録ターゲット
aws ssm register-task-with-maintenance-window \
--window-id <WINDOW_ID> \
--task-type RUN_COMMAND \
--task-arn "AWS-RunPatchBaseline" \
--service-role-arn <MW_ROLE_ARN> \
--priority 2 --name "run-patch-baseline" \
--targets "Key=WindowTargetIds,Values=<WINDOW_TARGET_ID>" \
--max-concurrency 1 --max-errors 1 \
--task-invocation-parameters '{"RunCommand":{"Parameters":{"Operation":["Install"],"RebootOption":["RebootIfNeeded"]}}}' \
--region ap-northeast-1
# 優先度3: 停止(Automation。再び落とす)
aws ssm register-task-with-maintenance-window \
--window-id <WINDOW_ID> \
--task-type AUTOMATION \
--task-arn "AWS-StopEC2Instance" \
--service-role-arn <MW_ROLE_ARN> \
--priority 3 --name "stop-instances" \
--task-invocation-parameters '{"Automation":{"Parameters":{"InstanceId":["<INSTANCE_ID>","<NAT_INSTANCE_ID>"]}}}' \
--region ap-northeast-1
優先度の数字が小さいほど先に実行され、1 → 2 → 3 の順になります。パッチタスクの Operation=Install は Scan も内包します(未適用を調べてから当てる)。
再起動(RebootIfNeeded)が走っても、Kiro Crew は systemd + linger で起動すれば自動復帰する設計なので、最後の停止タスクで落ちたあとも、次に起動すれば受付状態に戻ります。再起動必須のパッチ(カーネルなど)も、週1の深夜に自動で当たり切ります。
Patch Manager やメンテナンスウィンドウの利用自体は追加料金なしです(Systems Manager の標準機能の範囲)。この構成で増えるのは、週1回・深夜・十数分だけ EC2 が自動起動するぶんの時間課金だけで、r6a.large でも月数円レベル。「使うときだけ起動」の思想にはほぼ穴を開けません。
長期間ずっと落としっぱなしにすると、その間は Scan もされず、Patch Manager 上は非準拠のまま・Inspector の目録も古いままになります。「起動 → パッチ → 停止」を週1で挟むことで、実質は毎週リフレッシュされる、という考え方です。
これで設定は完了です。あとは日曜 AM3:00 に初回ウィンドウが自動で回るのを待つばかりです。
CVE を見える状態にする
パッチを当てる仕組みができても、「そもそも何が危ないのか」を検知できなければ片手落ちです。DISABLED になっていた Inspector を、EC2 スキャンに絞って有効化します。ECR や Lambda まで広げると課金が増えるので、まずは EC2 だけです。
Inspector の EC2 スキャンは SSM Agent 経由で動きます。出発点で見つかった「SSM Agent が旧版」が効いてくるので、先に Agent を最新へ更新しておきます。
# SSM Agent を最新に更新(Inspector の目録精度のため)
aws ssm send-command \
--document-name "AWS-UpdateSSMAgent" \
--instance-ids <INSTANCE_ID> <NAT_INSTANCE_ID> \
--region ap-northeast-1
その上で、Inspector の EC2 スキャンだけを有効化します。
# EC2 スキャンだけ有効化
aws inspector2 enable \
--resource-types EC2 \
--account-ids <ACCOUNT_ID> \
--region ap-northeast-1
有効化できたかと、対象インスタンスがスキャン対象(カバレッジ)に載ったかを確認します。
# ec2 が ENABLED になったか
aws inspector2 batch-get-account-status \
--account-ids <ACCOUNT_ID> --region ap-northeast-1
# カバレッジに対象インスタンスが載ったか(反映に時間がかかる)
aws inspector2 list-coverage --region ap-northeast-1 \
--query "coveredResources[?resourceId=='<INSTANCE_ID>']"
カバレッジに載ると、既知 CVE との突き合わせが始まります。
実際に有効化すると、数時間後にはカバレッジに今回対象のインスタンスが載り、初回スキャンで CVE の検出結果が並びました。ダッシュボードは次の通り(「パッチ適用前」の状態)。
本体と NAT の2台ともスキャン対象に載っています。検出結果は全体で 2,014 件。緊急(CRITICAL)は 0 件で、ここから後半のパッチ適用でどれだけ減らせるかを見ていきます。
Inspector は「有効化した瞬間に全部出そろう」わけではなく、カバレッジへの反映と初回スキャンに少し時間がかかります(SSM の目録収集は既定6時間ごと)。有効化した当日ではなく、翌日に結果を見るくらいの気持ちがちょうどよいです。
ここまでで「検知(Inspector)」と「適用(Patch Manager)」の仕組みが揃いました。
リスク検知時に通知する
検知を有効にしても、気づく経路がなければ意味がありません。続いて SNS でセキュリティ用のトピックを追加していきます。
まずはトライアルということで、検知を通知につなぐ最小構成を設計しました。
Amazon Inspector(CVE を検知)
│ finding(検出イベント)
▼
Amazon EventBridge(ルールで CRITICAL / HIGH だけ拾う)
│
▼
Amazon SNS(セキュリティ専用トピック)→ メール / チャット通知
セキュリティ通知のために専用トピックを1本立てます。まず SNS トピックを作り、自分のメールアドレスを購読します。
# セキュリティ専用トピックを作成し、メールを購読
aws sns create-topic --name kiro-crew-security-report --region ap-northeast-1
aws sns subscribe \
--topic-arn <TOPIC_ARN> --protocol email \
--notification-endpoint <YOUR_EMAIL> --region ap-northeast-1
設定したメールアドレスに確認メールが届きますので、リンクを踏んで有効化しておきます。
続いて、EventBridge からこのトピックへ Publish できるよう、トピックポリシーを付けます。
# events.amazonaws.com からの Publish を許可
aws sns set-topic-attributes --topic-arn <TOPIC_ARN> \
--attribute-name Policy \
--attribute-value '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"events.amazonaws.com"},"Action":"sns:Publish","Resource":"<TOPIC_ARN>"}]}' \
--region ap-northeast-1
次に EventBridge のルールで、
-
sourceがaws.inspector2 - 深刻度が CRITICAL か HIGH
の finding だけを拾い、このトピックに流します。
# CRITICAL / HIGH の Inspector finding だけを拾って SNS へ
aws events put-rule --name kiro-crew-inspector-high --region ap-northeast-1 \
--event-pattern '{"source":["aws.inspector2"],"detail-type":["Inspector2 Finding"],"detail":{"severity":["CRITICAL","HIGH"],"status":["ACTIVE"]}}'
aws events put-targets --rule kiro-crew-inspector-high \
--targets "Id=sns,Arn=<TOPIC_ARN>" --region ap-northeast-1
これで「検知 → 通知 → 適用」の輪がつながりました。実際に Inspector を有効化すると、初回スキャンの CRITICAL / HIGH がさっそくメールで飛んできます。
Inspector 有効化直後の初回スキャンは、既知 CVE を一気に棚卸しするため、CRITICAL / HIGH に絞っても件数分のメールがまとめて届きます(筆者の環境では数百通〜)。
自動適用の結果
ここまでで、Inspector を有効化して CVE が見えるようになり、パッチの週1ウィンドウ(日曜 AM3:00)もセットしました。あとはウィンドウが回って、起動 → パッチ → 停止が自動で通り、未適用パッチと finding が減っていくはずです。
本番は日曜 AM3:00 の自動実行ですが、記事化にあたってはスケジュールを一時的に手元の時刻へ寄せて1回手動で発火させ、動作を確認しました。発火のトリガーが手動なだけで、起動 → パッチ → 停止が1本で流れる挙動は本番と同じです。実際にどうなったかを見ていきます。
週1ウィンドウの自動実行
まず、止めてあった EC2 が週1ウィンドウで自動的に起こされ、パッチが当たり、また停止したか。メンテナンスウィンドウの実行履歴を確認します。
aws ssm describe-maintenance-window-executions \
--window-id <WINDOW_ID> --region ap-northeast-1
結果は、ウィンドウ全体が SUCCESS。3タスクとも成功で、起動 + Online 待ち(約1分)→ パッチ(Install・再起動あり)→ 停止が1本で通りました。終わったあとは2台とも stopped に戻っており、「止める運用」も崩れていません。
適用前後の変化
パッチでパッケージが新しくなると、Inspector が自動で再スキャンして finding を閉じにいきます。Inspector の EC2 スキャンは「パッケージ更新の直後に再スキャンして、脆弱性が残っていないか確認する」挙動なので、パッチ適用がそのまま再評価のトリガーになります。
未適用パッチの準拠状態と、CRITICAL / HIGH の finding 件数をそれぞれ確認します。
# パッチの準拠状態(未適用がいくつ残っているか)
aws ssm describe-instance-patch-states \
--instance-ids <INSTANCE_ID> <NAT_INSTANCE_ID> --region ap-northeast-1
# Inspector の CRITICAL / HIGH finding
aws inspector2 list-findings --region ap-northeast-1 \
--filter-criteria '{"severity":[{"comparison":"EQUALS","value":"CRITICAL"},{"comparison":"EQUALS","value":"HIGH"}]}' \
--query "length(findings)"
ウィンドウが通ったあと、パッチの準拠状態はこうなりました。2台とも未適用パッチ 0・失敗 0です。
| インスタンス | 適用済み | 未適用(Missing) | 失敗 |
|---|---|---|---|
| Kiro Crew(本体) | 104 | 0 | 0 |
| NAT インスタンス | 82 | 0 | 0 |
Inspector の finding も、パッチでパッケージが新しくなれば再スキャンで閉じられていきます。再スキャンの反映には少し時間がかかる(数十分〜数時間)ので、しばらく置いてから数えました。
CRITICAL / HIGH(今回 CRITICAL は 0 件なので実質 HIGH)はこのようになりました。
| タイミング | CRITICAL / HIGH の finding |
|---|---|
| Inspector 有効化直後(適用前) | 114 件 |
| パッチ適用後(再スキャン反映後) | 58 件(HIGH 56 件が解消) |
また、適用後の Inspector ダッシュボードは次のようになっておりました。
検出結果全体で見ると、適用前の 2,014 件が、966 件まで減りました。パッチ適用することでおおよそ半減となっています。
残った finding は「直せない」のではなく「追いかけ待ち」
残った HIGH 58 件を調べると、全件が fixAvailable: YES、中身はすべて kernel6.18 の CVE でした。「修正パッケージが無いから残った」のではありません。実機を覗くとこんな状況でした。
- 稼働中カーネル:
6.18.51(今回のパッチで当たったところまで) - 残 58 件の修正版カーネル:
6.18.54以降 - パッチ適用時点では、修正版カーネルはまだリポジトリに降りてきていなかった
つまり、CVE の公開とパッケージ配布のタイムラグによるものでした。次のパッチ自動適用でこの大半が解消される見込みです。
これはむしろ、この仕組みの価値を裏づけています。CVE は後から後から出て、修正版も後から降ってきます。だから「一度当てて終わり」ではなく、毎週起動 → パッチ → 停止を自動で回して追いかけ続けることに意味があります。
予算感
ここまで効果は確認できました。あとは、この守りがいくらで回るかです。今回の環境は ALB や NAT Gateway の固定費を削って月額利用料を抑えたので、ここに重いコストを足しては本末転倒です。調べた結果は次の通り。
| サービス | 課金 | この環境(EC2 2台)の目安 |
|---|---|---|
| Amazon Inspector(EC2 継続スキャン) | 約 $1.3 / インスタンス・月 | 2台で約 $2.6 |
| Patch Manager / メンテナンスウィンドウ | 無料 | $0 |
| EventBridge(AWS サービス発のイベント) | 無料 | $0 |
| SNS | 月100万パブリッシュ・メール1000通まで無料枠 | 実質 $0 |
課金が乗るのは実質 Inspector だけで、本体と NAT の2台ぶんでも月 $2〜3 程度でした。
まとめ
PHP / Node.js の騒動をきっかけに、自分の Kiro Crew 環境へ EC2 の脆弱性管理を入れてみました。
今回やったことは次の3つです。
- Inspector を有効化して CVE を検知
- Patch Manager の週1自動適用(起動 → パッチ → 停止)で当てる
- EventBridge + SNS で CRITICAL / HIGH をメール通知
最低限の守りは確保できたと思いますので、しばらくはこの状態で回してみて、問題点が出てこればもう少し改善してみようと考えています。







