脆弱性診断は、動いているWebアプリに外から攻撃の形をしたリクエストを送り、反応から欠陥を見つける作業です。セキュリティレビューは、設計書やソースコードを読んで欠陥を見つける作業です。どちらも「脆弱性を探す」点は同じですが、見ている物が違うので、見つかる物も違います。診断の報告書に重大な指摘が無くても、他人の注文が見えてしまう欠陥はそのまま残ることがあります。ツールは「この注文は誰の物か」を知らないからです。

そこで、SQLインジェクション・反射型XSS・IDOR(他人のデータをIDの書き換えで読める欠陥)を1つずつ仕込んだ43行のアプリを書き、SAST(Semgrep)、簡易のDASTプローブ、2つのアカウントを使った手動テストを当てて、どれが何を見つけるかを確かめました。

本記事は連載「事故から逆算するWebセキュリティ」の第19回です。セキュリティ全体の地図は「サプライチェーン攻撃とは|手口・防御ツール比較・npm 12の新機構まで実践解説」にまとめています。

WHERE TO LOOKどこで何を見るか

レビューは作る前と書いた物を、診断は動いている物を見る

  1. 設計

    設計レビュー・脅威モデリング

  2. コード

    コードレビュー・SAST

  3. 依存

    SCA(既知の脆弱性)

  4. 動くアプリ

    DAST・手動診断

  5. 本番の設定

    クラウド設定レビュー

出典: OWASP WSTG(Balanced Testing Approach)・OWASP SAMM v2(Verification)をもとに当サイトが整理

30秒でわかる

  • ・違い:レビューは設計とコードを読む。脆弱性診断は動いているアプリに外から試す。見つかる物が違う
  • ・物差し:OWASP ASVS 5.0.0(2025年5月公表)。L1は70要件、L2は183要件、L3は92要件(当サイトが数えた値)
  • ・実測:SQLiとXSSはSASTとプローブの両方で検出。IDORはどちらも0件で、2アカウントの手動テストでだけ出た
  • ・準備:対象外の一覧、役割の違うテストアカウント、検証用データ、止めるときの連絡先
  • ・回し方:PRごとにSASTとSCA、認可は自前のテストに、大きな変更の前に手動診断、指摘は再診断で閉じる

設計時のレビューと公開前の診断が何を見逃しうるかは、アフラックの調査結果に出ています。7月31日の報告は、攻撃が通常の利用と同じ形式のアクセスで行われたこと、短時間の大量照会への監視・制御が足りなかったことに加え、設計時のセキュリティレビューや侵入テストでもこの手口のリスクを想定できていなかったと説明し、再発防止策にレビューと侵入テストの強化を挙げました(漏えいは約440万人)。

脆弱性診断とセキュリティレビューの違い|何を物差しにするか

セキュリティレビューは中身が見える状態で行います。設計の段階なら「誰がどのデータに触れてよいか」「どこで外部の入力を受けるか」を図にして脅威を洗い出し(脅威モデリング)、実装の段階ならコードを人が読むか、SASTと呼ばれる静的解析ツールで読ませます。

脆弱性診断は、動いているアプリを相手にします。DASTと呼ばれるツールで自動的にリクエストを送る診断と、人が手でリクエストを組み立てて試す手動診断があります。悪用できるかまで踏み込んで試すものをペネトレーションテストと呼ぶこともあります。

何を確かめれば十分かの物差しには、OWASP の ASVS(Application Security Verification Standard) が使えます。現行は 5.0.0 で、表紙にある公表日は2025年5月です(GitHub の OWASP/ASVS のタグ v5.0.0_release と、5.0/en の文書で確認)。要件は3段階のレベルに分かれ、文書の表を数えると L1 が70、L2 が183、L3 が92、計345でした。ASVS は L1 を「最低限の出発点」、L2 を「ほとんどのアプリが目指すべき水準」としています。

ASVS は検証のしかたについても書いています。

・L1 であっても、文書やコードを見ないブラックボックスの外部テストで検証できるとは限らない
・DAST や SAST は出力のエンコードのような単純な要件には使えるが、業務ロジックとアクセス制御に関わる要件の多くは検証できない
・「自動化でテストできる」は「既製のツールを回す」と同じではない。アプリ固有の検証を単体・結合テストのように書く
・開発者と文書にアクセスできるハイブリッド型のペネトレーションテストを強く勧める

今回仕込んだ3つは、ASVS ではすべて L1 の要件に当たります。SQLインジェクションは 1.2.4(パラメータ化したクエリ)、反射型XSSは 1.2.1(文脈に合った出力エンコード)、IDORは 8.2.2(データ単位のアクセス制限。要件文に IDOR と BOLA が明記)です。最低限の水準の中にも、ツールだけでは確かめられない要件が入っています。

6つの活動の比較|脆弱性診断とレビューで何が見つかり、何を見逃すか

OWASP のテストガイド WSTG は、DAST・SAST・依存関係スキャンは必要だが単独では足りず、ツールは既知のシグネチャ型の脆弱性に強い一方で業務ロジックや文脈を理解しない、と書いています。そのうえで、設計レビュー、脅威モデリング、ソースコードレビュー、ペネトレーションテスト、自動スキャンを組み合わせるよう求めています(GitHub の OWASP/wstg の main で確認。安定版のタグは v4.2)。

活動 主に見つかるもの 見逃しやすいもの いつ 誰が
設計レビュー・脅威モデリング 認可の決め漏れ、信頼境界の抜け、業務の流れの穴 実装のミス全般 設計の段階・大きな変更の前 設計者+セキュリティに詳しい人
コードレビュー(人) 認可チェックの抜け、業務ロジックの誤り、暗号の誤用 量の多い単純なミス、設定や本番だけの問題 PRごと 同じチームの開発者
SAST 文字列連結のSQL、エスケープ漏れなど形で分かる欠陥 権限の意味、ルールが知らない書き方(誤検知も出る) PRごと・CI ツール+結果を見る人
SCA(依存関係) 既知のCVEがある依存ライブラリ 自分のコードの欠陥、未公表の欠陥 毎ビルド・定期 ツール+更新する人
DAST(ツール診断) 反射型XSS、エラーの漏れ、ヘッダーの不足 画面からたどれない機能、権限の意味、業務ロジック ステージングに出したあと ツール+診断者
手動診断・ペネトレーションテスト IDOR、権限昇格、業務ロジックの悪用、欠陥の組み合わせ 時間内に触れなかった範囲 リリース前・大きな変更のあと 社内外の診断者
クラウド設定レビュー 公開されたストレージ、広すぎる権限、ログの無効化 アプリ内部の欠陥 構成を変えたとき・定期 インフラ担当

WSTG の業務ロジックの章は、この種の欠陥は脆弱性スキャナーでは検出できず、診断者の技能と創造性に頼るとまで書いています。開発の成熟度モデル OWASP SAMM v2 も、検証の機能を Architecture Assessment(設計の評価)、Requirements-driven Testing(要件にもとづくテスト)、Security Testing(自動化で基本的な問題を見つけ、手動テストを複雑な攻撃に集中させる)の3つに分けています(GitHub の owaspsamm/core のモデル定義で確認)。

米国 NIST の SSDF(SP 800-218 v1.1)も、人が読めるコードのレビュー・解析(PW.7)と、実行できるコードのテスト(PW.8)を別の実践として並べています。コードレビューの進め方は OWASP Code Review Guide v2(2017年7月版)が詳しく、スキャナーが進歩しても人によるセキュリティコードレビューの位置づけは変わらないと書いています。

AIにコードを読ませて脆弱性を探す道具も増えています。コードを読んでから攻撃する「Shannonとは|ソースコードを読んでから攻撃するwhite-box設計のAIペンテスター」、「Vercel DeepSec入門|AIコーディングエージェントで脆弱性を検出するOSSセキュリティハーネス」、PRごとに差分を読む「Claude Code Security Reviewとは|PRを自動で守るGitHub Actionを実測」はどれも、表のどの行を肩代わりするのかを見て選ぶと位置づけがはっきりします。自律型の診断をどう統制するかは「OWASP APTS|AIエージェント時代の自律型ペネトレーションテスト基準を読む」で扱いました。

実測|SAST・DASTプローブ・2アカウントの手動テスト

検証用のアプリは Flask 3.1.3 で書いた43行です。X-Token ヘッダーで alice と bob の2アカウントを区別し、/search?q= に f文字列で組み立てたSQL、/hello?name= に入力をそのまま埋めたHTML、/orders/<id> にログインだけ確かめて持ち主を見ない処理を置きました。環境は Linux(x86_64・4コア)、Python 3.13 です。

SAST:Semgrep 1.181.0

ルールは GitHub の semgrep/semgrep-rules を git clone して、その python/ ディレクトリ(378ルール)を手元で指定しました。

pip install semgrep    # 1.181.0
git clone --depth 1 https://github.com/semgrep/semgrep-rules.git rules
semgrep scan --config rules/python --metrics=off app.py
# → 8 Code Findings(25・26行目のSQLに5件、32行目のHTMLに3件。/orders は0件)

8件はSQLの行とHTMLの行に集中し、2つの欠陥を指していました。うち3件は Django や SQLAlchemy 向けのルールが Flask のコードに当たったもので、同じ欠陥への重複です。IDORは0件でした。SQLの組み立て方は形で分かりますが、「この注文を alice が読んでよいか」はコードの形に現れないからです。

直したあとの確認も試しました。SQLをプレースホルダに、IDORを持ち主の条件つきに直し、XSSを markupsafe.escape(name) との文字列連結で直した版では、XSSのルール3件が残りました。ルールが「無害化した」と認める関数は jinja2.escape と flask.escape で、手元の Flask 3.1.3 と Jinja2 3.1.6 にはどちらも存在しません(hasattr で確認)。正しく直しても警告が残る、典型的な誤検知です。しかもこの版はタグまでエスケープされて見出しが崩れていました。Markup("<h1>こんにちは {} さん</h1>").format(name) に直すと0件になり、表示も正しくなりました。止めたい警告は行末の # nosemgrep で黙らせられます(試すと8件が5件になった)が、理由を書かずに付けると次の人が判断できません。

DAST:自作の簡易プローブ

OWASP ZAP のベースラインスキャンは動かしていません(未検証)。ZAP の公式ドキュメントによると、ベースラインスキャンは既定で1分間クロールしたあと受動スキャンを待つだけで、攻撃は送りません。攻撃を送るのはフルスキャン(能動スキャン)です。ベースラインだけ回して「SQLインジェクションは無かった」と読むのは誤りです。

代わりに、URLの一覧に対してXSSの文字列が反射するか、' で500になり ' OR '1'='1 で件数が増えるかを見る30行ほどのプローブを書きました。

python3 probe.py              # トークン無し
# /search: 401(ログインの外。調べられない)
# /hello: 反射型XSSの疑い(入力がそのまま返った)
# /orders/1: 401(ログインの外。調べられない)
python3 probe.py tok-alice    # alice でログインした状態
# /search: SQLインジェクションの疑い(' で500、' OR '1'='1 で件数が増えた)
# /hello: 反射型XSSの疑い(入力がそのまま返った)
# /orders/1: 200(引数が無い。送る攻撃の形が無い)

ログインさせないと、ログインの内側にある /search は調べられませんでした。ログインさせると SQLi も出ましたが、/orders/1 には送る攻撃の形がありません。1つのアカウントで見る限り、200 は正常な応答です。

手動:2つのアカウントで他人の注文を読む

WSTG の IDOR の項(WSTG-ATHZ-04)は、少なくとも2人のユーザーを用意し、片方が持つオブジェクトをもう片方で取りに行くよう書いています。その通りに試しました。

curl -s -H "X-Token: tok-alice" http://127.0.0.1:5055/orders/2
# [2,"bob","薬"]   ← alice のトークンで bob の注文が 200 で読めた=IDOR

持ち主の条件を足した版では、同じリクエストが 404、bob 本人なら 200 でした。陽性・陰性の両方を見たので、この手順で IDOR の有無を判定できています。

WHAT THE TOOLS MISSEDツールを抜けたもの

IDORだけは2アカウントの手動テストで出た

SAST+DASTプローブ

  • SQLif文字列のSQL 両方で検出/search?q
  • XSS入力をそのままHTMLへ 両方で検出/hello?name
  • IDOR他人の注文ID 両方とも0件/orders/2
出典: 当サイトの実測(2026-10-17・Semgrep 1.181.0・semgrep-rules 378ルール・自作プローブ)

脆弱性診断の準備と報告書の読み方|スコープ・2アカウント・再診断

脆弱性診断は、準備でどこまで見られるかが決まります。今回のプローブがログインの外では何も調べられなかったのと同じで、診断者に渡したアカウントと範囲の外側は報告書に出てきません。

BEFORE THE TEST診断の前に渡すもの

脆弱性診断は準備で見つかる量が変わる

  1. 1.対象・対象外と環境

    URL・API・ステージングの範囲。外部サービスと決済は対象外に

  2. 2.役割の違うテストアカウント

    同じ役割を2人+管理者。他人のデータを試せる

  3. 3.止めるときの連絡先

    診断中に障害が出たら誰に連絡し、誰が止めると決めるか

出典: OWASP WSTG(WSTG-ATHZ-04)・OWASP ASVS 5.0.0(検証の範囲)

・範囲:ASVS は、どのレベルを目指し、どの要件を検証に含めたかを「含めたもの」の側から明示するよう求めています。対象外(外部の決済・メール配信・他社API)も書き出します
・アカウント:同じ役割を2人と、権限の違う役割(管理者など)を用意します。1人分しか渡さないと、IDORは原理的に試せません
・環境とデータ:本番と分けた検証環境に、消えても困らないデータを入れます。能動スキャンは大量のフォーム送信やメール送信を起こすことがあります
・連絡先:診断中に障害が出たとき誰に連絡し、誰が止めると決めるかを決めておきます
・渡す資料:設計書や画面一覧、可能ならソースコードも渡すと、ASVS が勧めるハイブリッド型になります

国内の公的な資料としては、IPA が「安全なウェブサイトの作り方」と、その別冊として診断項目を絞り込んだ「ウェブ健康診断仕様」を公開しています。

報告書を受け取ったら、次の順で読みます。WSTG は、何をどう試したか、根本原因、直し方、深刻度、業務への影響を報告書に書くよう求めています。ASVS は、失敗だけを並べる従来のペネトレーションテストの報告と違い、ASVS にもとづく認証の報告書には範囲と確認した要件の全体を載せるよう求めています。

・範囲を先に読む:指摘が無い項目が「試して問題なし」なのか「試していない」のかを分けます
・再現する:報告書の手順を自分の環境でなぞり、影響範囲(どのデータが、どの役割から、何件見えるか)を確かめます
・深刻度は件数でなく中身で並べる:WSTG は、利用者データに届く認可の回避1件は、低リスクのXSS数十件より重いと書いています
・同じ型を全体で探す:指摘は1か所でも、根本原因は他の画面にもあることが多いので、SASTのルールやgrepで横に広げます
・再診断で閉じる:直したら、報告書と同じ手順で再び試してもらいます。今回の404と200のように、陰性と陽性の両方を確かめます

対策|小さなチームでレビューと脆弱性診断を回す手順

専任のセキュリティ担当がいない数人のチームでも、表の活動を時期に割り振れば回せます。物差しは ASVS の L1 から始め、扱うデータに応じて L2 の要件を足していきます。

・設計のとき:新しい画面やAPIごとに「誰がどのデータを読めて、書けるか」を1〜2行で書きます。ASVS 8.1.1(L1)は、関数単位とデータ単位の認可のルールを文書にするよう求めています
・PRのたび:CIでSASTとSCAを回し、レビューでは認可のチェックを人が見ます。誤検知を黙らせるときは理由を一緒に書きます
・認可はテストに書く:今回の2アカウントのcurlを結合テストにして毎回走らせます。ASVS が言う「アプリ固有の検証を自動化する」の最小形です
・大きな変更の前:ログインの内側まで含めた手動診断を頼み、アカウントと資料を渡します
・定期的に:クラウドの設定と依存ライブラリを見直し、診断の指摘は再診断が通るまでチケットを閉じません

ツールが0件を返したときに言えるのは「ツールが知っている形の欠陥は見つからなかった」ことだけです。他人のデータを読めるかどうかは、2つのアカウントで実際に試すまで分かりません。

参照ソース