AWS Continuum for penetration testingの検出結果の再検証を試してみた

AWS Continuum for penetration testingの検出結果の再検証を試してみた

AWS Continuum for penetration testingに追加された検出結果の再検証を、実際の検出結果に対してインフラ側の修正とアプリ側での修正の2パターンを入れて挙動を確認しました。
2026.10.18

はじめに

クラウド事業統括本部の浅野です。

2026年8月に、AWS Continuum for penetration testingへ検出結果(finding)の個別再検証(Revalidation)が追加されました。

https://aws.amazon.com/about-aws/whats-new/2026/08/aws-security-agent/

同時に追加されたタスク時間の上限設定については、こちらの記事で扱っています。


https://dev.classmethod.jp/articles/2026-10-18-security-agent-pentest-budget/

これまでは、指摘された脆弱性を直したあとに本当に直ったかを確かめようとすると、ペネトレーションテストをまるごと回し直すしかありませんでした。再検証を使うと、選んだ検出結果だけを再テストして、まだ再現するか(Active)、再現しなくなったか(Resolved)を短時間で判定してくれます。

実際にどう動くのか見たかったので、ペネトレーションテストで検出された脆弱性1件に対して修正を入れ、再検証を試してみました。

再検証とは

公式ドキュメントに書かれている内容をまとめると、次のとおりです。

https://docs.aws.amazon.com/securityagent/latest/userguide/revalidate-findings.html

  • 脆弱性を修正したあとに、その検出結果がまだ再現するかを確認するための機能
  • 選んだ検出結果の検証手順だけを、認証し直したうえで再実行する。新しい脆弱性の探索やほかの範囲のテストはしない
  • 結果はActive(再現した=まだ攻撃可能)とResolved(再現しなかった=修正が効いている)の2種類
  • 再検証は別のジョブとして動き、元の検出結果は変更されない。元の検出結果に再検証の履歴がひも付く

ポイントは料金で、1つの検出結果に対して最初の3回まで再検証は無料です。修正→再検証を3回まで繰り返すぶんには費用を気にせず使えます。

https://aws.amazon.com/security-agent/pricing/

構成

今回の構成図です。

2026-10-18-security-agent-pentest-finding-revalidation-01

検証対象アプリはOWASP Juice Shopです。意図的に脆弱性を埋め込んだECサイト型のWebアプリで、公式のコンテナイメージをALB + ECS Fargateにデプロイし、Route 53のサブドメインを割り当ててHTTPで公開しました。環境の構成やペネトレーションテストの作成・実行の手順は、上で紹介したタスク時間の上限設定の記事と同じです。

https://owasp.org/www-project-juice-shop/

やってみた

再検証する検出結果を選ぶ

ペネトレーションテストを実行して、重要8件・高8件・中程度14件の検出結果が出ている状態です。

2026-10-18-security-agent-pentest-finding-revalidation-02

このうち、再検証の対象には重大度Highの「未認証のGET /rest/memoriesで他ユーザーの情報が漏洩する」を選びました。レポートに載っている検出結果の詳細です。

2026-10-18-security-agent-pentest-finding-revalidation-03

項目 内容
検出結果 Unauthenticated GET /rest/memories leaks other users' emails, MD5 password hashes, and deluxe tokens
重大度 / 確信度 High(リスクスコア7.5)/ 高
リスクタイプ 情報の開示

Juice Shopには「Photo Wall」という、ユーザーが写真(メモリ)を投稿して一覧で見られる機能があり、その一覧を返すAPIがGET /rest/memoriesです。エージェントの指摘は、このAPIがログインなしで呼べるうえに、メモリと一緒に投稿者のユーザー情報をまるごと返してしまっている、というものでした。そのため、ログインしていない誰でも、メモリを投稿したユーザーのメールアドレス・パスワードハッシュ(ソルトなしのMD5なので解析されやすい)・ロール・会員トークンを取得できてしまいます。同じアプリのユーザー一覧API(/api/Users)はログインが必須で、パスワードなどの属性も除いて返しているので、このAPIだけ対策が漏れている、という比較も書かれていました。推奨される修正としては、このAPIで返すユーザー情報をIDや表示名などに限定すること、ユーザーモデル側で機微な属性を常に除くようにすること、漏れたトークンのローテーションが挙げられています。

未認証のGET 1回で確認できて、データを書き換えない脆弱性なので、修正と再検証の往復を見るのに向いていると思って選びました。

修正を入れる(1回目: インフラ側だけ)

まずはアプリに手を入れずに、インフラ側だけで手抜きに直してみました。修正前は、ログインなしでGET /rest/memoriesを叩くと200が返り、レスポンスにメモリ投稿者のメールアドレスやパスワードハッシュなどが含まれていました。これをALBのリスナールールで、/rest/memories配下へのアクセスには固定レスポンスで403を返すようにします。アプリに届く前にALBで止めてしまえば機密情報は返らなくなるはず、という考えです。Terraformでリスナールールを1本追加してterraform applyで反映しました。

リクエスト 修正前 修正後
GET /rest/memories(未認証) 200。ユーザー情報にメールアドレス・パスワードハッシュ・会員トークンを含む 403。アプリに届かず、ALBが{"error":"Forbidden"}を返す
そのほかのパス(トップ、商品一覧、検索など) 200 200(変更なし)

再検証を実行する

インフラの修正を適用したあと、元のペネトレーションテストの実行を開き、「検出結果」タブで対象の検出結果を選んで「1件の検出結果を再検証する」を押しました。

2026-10-18-security-agent-pentest-finding-revalidation-04

再検証は別のジョブとして実行され、実行履歴にはジョブタイプ「再検証」で並びました。元のペネトレーションテストの実行とはここで区別できます。

2026-10-18-security-agent-pentest-finding-revalidation-05

再検証ジョブの概要です。ステップは「プリフライト → 再検証 → ファイナライズ」の3段で、最大タスク時間などの設定は元のペネトレーションテストから引き継がれていました。

2026-10-18-security-agent-pentest-finding-revalidation-06

1回目の結果: Active

1回目の再検証が終わった時点の実行一覧です。再検証のジョブが「完了」になり、検出結果の概要には「高: 1」が出ています。

2026-10-18-security-agent-pentest-finding-revalidation-07

実行 開始 期間(実時間) タスク時間 検出結果 判定
フル実行 10/17 15:29 5時間19分 19時間56分 重要8・高8・中程度14 —
再検証(1回目) 10/18 13:10 38分 22分(0.37h) 高1(新しく起票された迂回経路) Active

まず元のペネトレーションテストの検出結果を開くと、「再検証ジョブ」の欄に今回のジョブがひも付いていて、結果は「アクティブ」のままでした。修正を入れたあとも脆弱性は残っている、という判定です。

2026-10-18-security-agent-pentest-finding-revalidation-08

次に再検証ジョブ側の「検出結果」タブを見ると、新しい検出結果が1件だけ載っていました。タイトルは「Unauthenticated GET /rest/Memories … (case-bypass of the /rest/memories 403 block)」で、パスのMが大文字になっています。

2026-10-18-security-agent-pentest-finding-revalidation-09

指摘されていたのは、/rest/memoriesは403で塞がれているが、/rest/Memoriesのように大文字小文字を変えたパスなら同じ情報が取れる、ということでした。前提として、ALBのリスナールールのパス条件は大文字と小文字を区別しますが、Juice ShopはNode.jsのExpressで動いていて、Expressのルーティングは既定で区別しません。そのため、大文字を混ぜたパスはALBのルールに一致せずにアプリまで届き、アプリ側では同じ処理が動いて同じ情報が返ります。検出結果の説明には、403にアプリ固有のヘッダがなく、管理者のトークンを付けても同じ403が返ることから手前のプロキシによる遮断だと判断し、大文字小文字を変えたパスを試して200と機密情報が返ることを確認した、という流れが書かれていました。今回のような手抜きの修正に対して、迂回経路までちゃんと試したうえで指摘してくれています。

1回目で分かったことをまとめます。

  • 再検証は、同じリクエストを再送してステータスコードを見るだけではなく、修正の仕方を見たうえで迂回を試す
  • 迂回できた場合は、元の検出結果をアクティブのままにしたうえで、迂回経路が新しい検出結果として起票される
  • エッジでパスを塞ぐだけの対処はこうして見抜かれるので、修正は本来の層(今回ならアプリ)で入れる必要がある

修正を入れ直す(2回目: アプリ側)

ALBのルールは外して、今度はアプリ側をちゃんと直しました。このルートがメモリと一緒に返していたユーザー情報を、ID・表示名・プロフィール画像だけに絞り、メールアドレスやパスワードハッシュなどを返さないようにしています。修正版をデプロイしたあと、/rest/memoriesも大文字の変種も、200は返すもののユーザー情報に機微な属性が含まれないことを確認しました。

再検証を実行する(2回目)

アプリ側の修正をデプロイしたあと、1回目と同じ手順で、元の検出結果をもう一度再検証しました。

2回目の結果: Resolved

2回目の再検証が終わった時点の実行一覧です。今度は検出結果が0件でした。

2026-10-18-security-agent-pentest-finding-revalidation-10

実行 開始 期間(実時間) タスク時間 検出結果 判定
フル実行 10/17 15:29 5時間19分 19時間56分 重要8・高8・中程度14 —
再検証(1回目: ALBで403) 10/18 13:10 38分 22分(0.37h) 高1(迂回経路) Active
再検証(2回目: アプリ側を修正) 10/18 14:08 32分 21分(0.35h) 0件 Resolved

再検証ジョブの概要画面でも、重大度レベルとリスクタイプのグラフがどちらも空になっています。

2026-10-18-security-agent-pentest-finding-revalidation-11

元の検出結果の画面では、ステータスの欄が「アクティブ」から「解決済み」に変わり、「再検証ジョブ」の欄の2回目のジョブに「解決済み」のラベルが付いていました。「履歴を表示する」を開くと、1回目(アクティブ)と2回目(解決済み)が並びます。修正の往復が1つの検出結果に残るので、あとから経緯を追えます。

2026-10-18-security-agent-pentest-finding-revalidation-12

再検証ジョブ側の「検出結果」タブには、「解決済み」のラベルが付いた記録が1件ありました。タイトルは「GET /rest/memories does NOT leak user credential material」で、エージェントが「漏洩しないことを確認した」という結論を検出結果の形で残したものです。説明を読むと、未認証で200が返ること自体は変わらず再現したうえで、レスポンス本文を全部調べて、メールアドレス・パスワードハッシュ・会員トークンがどれも含まれていないことを確認し、解決済みと判断していました。

2026-10-18-security-agent-pentest-finding-revalidation-13

2回目で分かったことをまとめます。

  • Resolvedの判定は、200か403かではなく、レスポンスの中身に機密情報が含まれなくなったことを確認して出される
  • 判定結果は元の検出結果の履歴に積み上がり、Active → Resolvedの経緯があとから追える
  • 所要は1回あたり30〜40分、0.4タスク時間弱。フル実行(5時間強・約20タスク時間)と比べると、修正のたびに回せる軽さ

補足が2つあります。

1回目の再検証で起票された/rest/Memoriesの検出結果は、今回の再検証の対象に選んでいないので、ステータスはアクティブのままです。アプリ側の修正で実際には直っていますが、再検証は選んだ検出結果しか判定しないので、こちらも解決済みにしたければ別途再検証する必要があります。

また、再検証ジョブからもレポート(PDF)を生成できますが、生成されたレポートには、再検証のジョブであることも、解決済みになった検出結果も載っていませんでした。判定の記録はコンソールの検出結果の画面か、次のAPIで確認します。

再検証ジョブをCLIで確認する

ジョブの詳細を取ると、種別が再検証であることと、再検証の対象になっている検出結果が分かります。

$ aws securityagent batch-get-pentest-jobs \
    --region ap-northeast-1 \
    --agent-space-id <agent-space-id> \
    --pentest-job-ids <revalidation-job-id> \
    --query 'pentestJobs[0].{status:status,jobType:jobType,selectedFindingIds:selectedFindingIds}'
{
    "status": "IN_PROGRESS",
    "jobType": "REVALIDATION",
    "selectedFindingIds": [
        "<finding-id>"
    ]
}

最後に

今回は、ペネトレーションテストで見つかった「未認証で/rest/memoriesを叩くと他ユーザーの情報が漏れる」という検出結果に対して、ALBでパスを塞ぐだけのインフラの修正と、アプリ側でレスポンスの属性を絞る修正の2パターンを入れて、それぞれ再検証してみました。

インフラだけの本質を押さえていない修正では、大文字小文字を変えたパスで迂回され、その迂回経路が新しい検出結果として起票されてActiveのままでした。アプリ側の修正は、レスポンスに機密情報が含まれなくなったことを確認され、狙い通りResolvedになりました。

所要はものにもよると思いますが再検証1回あたり30〜40分、タスク時間で20分ほどで、フル実行の5時間強・約20タスク時間に比べると、修正のたびに回せる軽さです。

注意点としては、再検証の対象になるのは選んだ検出結果だけなので、今回のように途中で新しい検出結果が増えた場合は、そちらも別で再検証しないとActiveのまま残ります。また、ResolvedになったことはコンソールとAPIでは確認できますが、生成したレポートには載らなかったので、記録として残したい場合はコンソールの履歴を使うことになります。料金は検出結果ごとに3回まで無料なので、今回のように修正と再検証を2回繰り返しても費用はかかりませんでした。

今回は以上です。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事