手動診断でしか見つからない脆弱性の話

※この記事は「エムティーアイ Blog Summer 2026」の 6/26 の記事です。

こんにちは、テクノロジー本部 Red Teamの石廣です。

今回は、Red Teamの強みでもある手動診断について、自動スキャンとの違いを交えながらお話しします。

目次

はじめに

「脆弱性診断ツール、導入してますよ」

そう言われることがあります。ZAP や Burp Suite などの自動スキャン、確かに有効なツールです。

でも、手動診断をしていると、ツールがまったく反応しなかった箇所に深刻な脆弱性が潜んでいることが珍しくありません。

この記事では、診断員の視点から「自動スキャンで見つかること・見つからないこと」を整理します。

自動スキャンが得意なこと

まず公平に評価します。自動スキャンは以下が得意です。

得意な検出
既知のパターンマッチング 単純なSQLi、反射型XSS
ヘッダーの設定不備 X-Frame-Options 欠如、CSP 未設定
TLS / 証明書の問題 古い暗号スイート、証明書期限
既知CVEのバージョン検出 脆弱なライブラリの特定
ディレクトリ・ファイルの列挙 .git 公開、バックアップファイル露出

広く浅く、高速に、繰り返し実行できる。CI/CD への組み込みと相性が良く、ベースラインのセキュリティ維持には欠かせません。

ただし、自動スキャンが「問題なし」と判断することと「実際に安全である」ことは別の話です。自動スキャンが沈黙するのは、「安全だから」ではなく「判断できないから」である場合が多くあります。

自動・手動・コードレビューの役割分担

手動診断の話をする前に、3つのアプローチの違いを整理します。

アプローチ 得意領域 苦手領域
自動スキャン 既知パターン・設定不備・網羅的な探索 ビジネスロジック・文脈依存の問題
手動診断 ロジック欠陥・攻撃連鎖・タイミング問題 広範囲のカバレッジ
コードレビュー セカンドオーダー・内部ロジックの欠陥 実行時の挙動・設定の問題

この3つは「どれかひとつで十分」ではなく、組み合わせることで互いの弱点を補う関係にあります。

自動スキャンが見落とすもの

ここからが本題です。手動診断でしか見つからない、または手動診断が大幅に有利な脆弱性タイプを6種紹介します。

1. ビジネスロジックの脆弱性

自動スキャンはアプリケーションの「意図」を理解できません。HTTPリクエストとレスポンスのパターンを機械的に評価するだけなので、仕様の文脈が必要な問題は素通りします。

例: 価格改ざん

POST /cart/checkout
Content-Type: application/json

{"item_id": 1, "quantity": 2, "price": 0}

price パラメータをクライアントから送っている場合、値を書き換えるとサーバーがそのまま処理してしまうことがあります。自動スキャンはこのフィールドが「本来クライアントから送るべきでない値」だとは判断できません。

なぜ自動スキャンで見つからないか: 自動スキャンはレスポンスの異常(エラー、予期しないデータ)を検知します。価格 0 円で注文が成功した場合、レスポンス自体は正常(HTTP 200)なので、問題として検出されません。

他の典型例:

  • ポイント・クーポンの二重利用
  • ワークフローのステップ飛ばし(支払い完了前に完了フラグを立てるなど)
  • 退会済みアカウントでの API 呼び出し継続
  • 数量に負の値を入れることで返金処理を発生させる

2. 複雑な IDOR(水平権限昇格)

IDOR(Insecure Direct Object Reference)とは、URLやパラメータのIDを操作することで他ユーザーのリソースにアクセスできてしまう脆弱性です。

単純な /user/123/user/124 の試みは自動スキャンでも検出できる場合があります。しかし実際の診断では、もっと複雑な形で現れます。

例: エクスポート機能の IDOR

GET /api/reports/export?report_id=5678

このエンドポイントで report_id が別ユーザーのものでも返却されるケース。自動スキャンは「そのIDが他のユーザーに属するかどうか」をアプリケーションの文脈なしには判断できません。

例: 間接的な参照を介した IDOR

# 直接IDを指定するのではなく、間接的に別ユーザーのデータを取得できるケース
GET /api/invoices/latest        # 自分の最新請求書を取得するつもりが
GET /api/invoices/latest?user_id=456  # 他ユーザーのものが取得できる

なぜ自動スキャンで見つからないか: 自動スキャンはIDを変えてリクエストを送ることはできますが、「返ってきたデータが別のユーザーのものかどうか」を判断できません。これを確認するには、2つのアカウントを使って相互に検証するという手動の手順が必要です。

診断員がやること: ユーザーAとユーザーBの2アカウントを用意し、一方のリソースIDをもう一方のセッションから叩く。単純ですが、手作業でないと確認できません。


3. 競合状態

処理のタイミングを突く攻撃です。チェックと実行の間に生まれる隙間を利用します。

例: ポイント消費の競合

残高 100pt で、100pt 消費するリクエストを同時に複数送信すると、残高チェックと消費処理の間に隙間が生まれ、残高が負になる、または二重に特典が付与されるケースがあります。

[Thread1] 残高確認: 100pt ✓
[Thread2] 残高確認: 100pt ✓  ← 同時に通過
[Thread1] 消費処理: -100pt
[Thread2] 消費処理: -100pt  ← 合計 200pt 消費、残高 -100pt

他の発生しやすいケース:

  • 抽選・先着機能(定員を超えた当選)
  • ファイルアップロードと処理の間のすり替え
  • パスワードリセットトークンの並列利用

なぜ自動スキャンで見つからないか: 自動スキャンは並列リクエストの送出はできても、「その結果がビジネス的に不正かどうか」の判定ができません。また、競合状態は発生タイミングに依存するため、再現性にもばらつきがあります。


4. 多段階の攻撃チェーン

単体では低リスクに見える複数の問題が組み合わさって、深刻な脆弱性になるケースです。診断員が「これ単体では低いけど、あそこと組み合わせると…」と考えながら調査する場面です。

例: Stored XSS への昇格

1. 自己プロフィールのメールアドレスに任意の値が設定できる(単体では低リスク)
2. 管理画面でメールアドレスを HTML としてそのまま出力している(単体では低リスク)
   → 組み合わせると管理者画面で Stored XSS が成立(高リスク)

例: 権限昇格への昇格

1. ユーザー登録APIのレスポンスに role フィールドが含まれている(情報漏洩・低)
2. ユーザー更新APIが role フィールドを受け付けてしまう(マスアサインメント・中)
   → 一般ユーザーが自分を管理者に昇格できる(高)

なぜ自動スキャンで見つからないか: 自動スキャンはそれぞれのエンドポイントを個別に評価します。エンドポイントをまたいだ因果関係は、人間がアプリケーションの仕様を読み解かないと見えてきません。


5. 認証フローのバイパス

パスワードリセット・メール認証・多要素認証など、複数ステップを持つ認証フローには、ステップ間の状態管理に問題が潜みやすいです。

例: パスワードリセットのトークン検証バイパス

正規フロー:
  Step1: メールアドレスを入力 → リセットメール送信
  Step2: メール内のリンクからトークンを検証
  Step3: 新しいパスワードを入力

攻撃フロー:
  Step1: メールアドレスを入力
  Step3: Step2をスキップして直接パスワード変更リクエストを送る
         → サーバーがStep2完了を確認していない場合、通ってしまう

例: MFA のバイパス

POST /auth/verify-otp
{"otp": "123456", "user_id": 999}  ← 他ユーザーのIDを指定

OTP の検証とユーザーの紐付けが適切でない場合、他ユーザーのアカウントに認証できてしまうことがあります。

なぜ自動スキャンで見つからないか: 自動スキャンは通常、フローを順番に実行します。ステップを意図的にスキップしたり、パラメータを差し替えたりする探索は、仕様を理解した人間が手動で行う必要があります。


6. SSRF の複雑なケース

SSRF は、サーバーに任意のURLへリクエストを送らせる脆弱性です。単純なケース(http://169.254.169.254/ などのメタデータエンドポイントへの直接アクセス)は自動スキャンでも検出できます。しかし実際の診断では、迂回が必要な複雑なケースが多くあります。

例: ブラックリストバイパス

# 直接指定はブロックされる
url=http://169.254.169.254/latest/meta-data/

# リダイレクト・エンコードを経由してバイパス
url=http://example.com/redirect           ← 攻撃者サーバーが内部アドレスにリダイレクト
url=http://169.254.169.254%0A/            ← 改行コードによるバイパス
url=http://2130706433/                    ← 127.0.0.1 の10進数表現

なぜ自動スキャンで見つからないか: 自動スキャンは既知のバイパスパターンをある程度試しますが、対象アプリケーションのフィルタリングロジックに応じた迂回手法の選択は、人間の判断が必要です。また、SSRF の影響範囲(どの内部サービスに到達できるか)の調査も手動で行います。


手動診断のアプローチ

手動診断は「なんとなくリクエストを変えてみる」ではありません。診断員はアプリケーションを理解しながら、仮説を立てて検証しています。

ステップ1: アプリケーションの理解

まず「このサービスは何をするものか」を把握します。

  • どんなユーザーロールが存在するか(一般・管理者・ゲストなど)
  • 金銭・ポイント・重要データを扱う機能はどこか
  • 外部サービスとの連携はあるか(ファイルアップロード先、外部API呼び出しなど)
  • どのエンドポイントが認証なしで叩けるか

ステップ2: 攻撃対象面の整理

全エンドポイントを列挙し、「どこが攻撃者にとって美味しいか」を優先度付けします。

高優先度:
  - 認証・認可に関わる処理
  - 他ユーザーのデータを参照・操作できる可能性がある処理
  - 金銭・ポイントを動かす処理
  - ファイルを受け取る処理
  - 外部URLを受け取る処理

低優先度:
  - 静的コンテンツの配信
  - 単純な参照のみのエンドポイント

ステップ3: 仮説を立てて検証する

「このパラメータを変えたらどうなるか」「このステップを飛ばしたらどうなるか」という仮説を立て、ひとつひとつ検証します。

自動スキャンが「パターンに一致するか」を見ているのに対し、診断員は「仕様の抜け穴はどこか」を考えています。

ステップ4: 低リスクな発見を組み合わせる

単体では報告しにくい小さな発見でも、組み合わせることで大きな問題になる可能性を常に考えます。これが多段階攻撃チェーンの発見につながります。


手動診断にも限界はある

ただし、手動診断にも以下の限界があります。

  • 時間とコストがかかる — 深く診るほど工数が増える。診断期間中に触れないエンドポイントも出てくる
  • 診断員のスキルに依存する — 見落としはゼロではない。診断員の経験・得意領域によって発見できる問題が変わる
  • スコープ外は見られない — 合意した範囲しか診断しない。インフラ・モバイルアプリ・ソースコードなど、スコープ設定が重要
  • スナップショットでしかない — 診断時点の状態を評価する。その後のリリースで新たな脆弱性が生まれることもある

手動診断は「全脆弱性を発見する」ものではありません。「自動では見つけにくい問題に集中して調べる」 というものです。自動スキャンと手動診断は競合ではなく、役割分担です。


まとめ

  • 自動スキャンは「既知パターン・設定不備」の発見に強い
  • 手動診断は「ビジネスロジック・攻撃連鎖・タイミング・認証フロー」に強い
  • 両者は競合ではなく役割分担。コードレビューも加えた3層で補完し合う
  • 手動診断にも限界はある。スコープ設定が診断の質を左右する

脆弱性診断はゴールではなく、安全なサービスを作り続けるための手段のひとつです。この記事が開発時のセキュリティ意識のきっかけになれば幸いです。


参考資料