現代Webセキュリティ完全ガイド|31の攻撃と脆弱性を設計、開発、運用で防ぐ

現代Webセキュリティ完全ガイド|31の攻撃と脆弱性を設計、開発、運用で防ぐ

#セキュリティ#Web開発#脆弱性対策

こんにちは、大平です。

「Webセキュリティ対策を強化したい」と考えたとき、多くの現場では脆弱性名の一覧から調査が始まります。 XSS、SQLインジェクション、CSRFなどを一つずつ確認し、見つかった問題を直していく進め方です。

ところが、脆弱性をいくつ覚えても、自社のシステムがどこで破られるかは見えてきません。 アプリケーションがSQLを安全に組み立てていても、APIの認可が抜けていれば他人の情報を読まれます。 ソースコードに問題がなくても、CIが悪意ある依存パッケージを取り込めば、秘密情報を盗まれます。 ログイン処理が正しくても、ブラウザ上でセッションを奪われれば、その後の操作は認証済みセッションからのものとして処理され、資格情報の正しさだけでは区別できません。

Webセキュリティとはシステムが何を、どこまで信頼するかを設計する仕事であり、脆弱性名の暗記ではありません。

この記事では、これまで公開してきた31本の個別解説を、ブラウザ、入力、認証、認可、状態、運用という六つの攻撃面に組み直します。 これに、個別脆弱性ではなく開発工程全体を狙うnpmサプライチェーン攻撃の解説も加えます。 各脆弱性の詳細は個別記事へ譲り、総論では「どこで起きるのか」「なぜ同じ対策原則が効くのか」「組織としてどう防ぐのか」をつなぎます。

もちろん、将来現れる攻撃手法まで含めた「あらゆる脆弱性の一覧」を一つの記事で作ることはできません。 ここで目指す完全ガイドとは、初めて聞く攻撃であっても、破られた境界と守るべき原則を判断できる地図です。

対象は、WebアプリケーションとHTTP APIを設計、開発、運用するうえで必要なアプリケーションセキュリティです。 クラウドアカウント全体のIAM、従業員端末、社内ネットワーク、物理セキュリティ、大規模DDoSへの回線対策、法令への適合判断までは網羅しません。 また、安全でないデシリアライゼーション、NoSQLインジェクション、HTTPリクエストスマグリング、OAuthやOIDCの実装不備など、個別記事にしていない攻撃もあります。 これらも、どの入力を信頼し、どの権限を渡し、どの異常を検知するかという同じ地図へ配置できます。

Webシステムの攻撃面を六つに分ける

利用者が画面を一度開くだけでも、リクエストはブラウザから、構成に応じてCDNやリバースプロキシを通り、アプリケーションへ届きます。 アプリケーションは利用者を識別し、権限を確認し、データベースやファイル、外部サービスへアクセスします。 生成したレスポンスは逆の経路を戻り、設定に応じてキャッシュされ、処理結果はログへ残ります。

この流れでは、ブラウザからサーバへ入るとき、匿名の利用者が認証済みになるとき、一般利用者が管理機能を呼ぶときなど、主体やデータへ与える信頼水準と権限が変わります。 その変化が起きる具体的な境目が信頼境界です。 この記事では、信頼境界を見つけやすくするために、攻撃面を六つの管理領域へ分けます。

攻撃面確認すること破られたときの代表的な結果
ブラウザと配信層どのオリジン、画面、レスポンスを信頼するかスクリプト実行、意図しない操作、偽サイトへの誘導
入力と実行系入力をデータとして扱い、命令へ変えないかSQL実行、コマンド実行、内部ネットワークへのアクセス
認証とセッション誰が操作しているかを正しく識別できるかアカウント乗っ取り、なりすまし
認可とデータその利用者に操作とデータを許してよいか他人の情報閲覧、管理機能の実行、機密属性の漏洩
状態と計算資源同時実行や大量入力でも制約を維持できるか二重処理、在庫や残高の破壊、サービス停止
開発と運用コード、依存関係、設定、秘密情報を信頼できるか本番侵害、認証情報の流出、攻撃の長期化

守るべき結果は、情報を許可なく読ませない機密性、情報や処理を許可なく変えさせない完全性、必要なときにサービスを使える可用性の三つに整理できます。 ただし、一つの脆弱性が一つの結果だけを壊すとは限りません。 XSSで認証済みセッションを悪用して個人情報を読めば機密性が破られ、その状態から送金されれば完全性も破られます。

脆弱性は単独で並んでいるのではなく、別の攻撃を成立させる部品にもなります。 だからこそ、個別対策とシステム全体の設計を分けるわけにはいきません。

ブラウザと配信経路にある攻撃面

ブラウザは、CookieのDomainPathSecureSameSiteなどの条件を宛先URLとリクエストの文脈が満たすと、そのCookieを自動で送り、受け取ったHTMLやJavaScriptを実行します。 CDNは、同じリクエストだと判断したレスポンスを複数の利用者へ再利用します。 どちらもWebを成立させる便利な仕組みですが、信頼の条件を誤ると攻撃者の入力まで正規の処理として扱います。

XSS(クロスサイトスクリプティング)は、攻撃者の入力を正規サイトのスクリプトとしてブラウザに実行させる脆弱性です。 HTML、属性値、URL、JavaScriptでは安全な表現が異なるため、出力先の文脈に合ったエスケープが必要です。 フレームワークの自動エスケープとtextContentなどの安全なDOM APIを使い、タグ名、イベントハンドラ、スクリプト本文など、外部入力を置くこと自体が危険な文脈は避けます。 リッチテキストなどHTMLを許可する場所では、実績のあるサニタイザで危険な要素と属性を除去します。 CSPは被害を抑える防御層になりますが、出力時のエスケープを置き換えるものではありません。

CSRF(クロスサイトリクエストフォージェリ)は、CookieやHTTP認証などをブラウザが自動で付与する性質を利用し、ログイン中の利用者へ意図しない操作を実行させます。 XSSが正規サイトの中で攻撃者のコードを動かすのに対し、CSRFは外部サイトから正規のリクエストに見える操作を送らせます。 状態を変える処理をGETで受け付けず、CSRFトークンを検証し、CookieのSameSite属性を適切に設定することで守ります。 SameSite属性は有力な防御層ですが、サブドメインを含む構成や対応ブラウザなどの条件に左右されるため、原則としてCSRFトークンやOriginSec-Fetch-Siteの検証と重ねます。 送金や認証情報の変更には再認証も重ねます。

画面そのものが正規でも、利用者の見え方は攻撃者が操作できます。 クリックジャッキングは、正規画面を透明なiframeで重ね、別のボタンを押したつもりの利用者へ重要操作を実行させる攻撃です。 HTTPレスポンスヘッダのCSP frame-ancestorsで埋め込み元を限定し、互換性が必要な環境ではX-Frame-Optionsも併用します。

オリジン間のデータ共有では、送信と読み取りを区別する必要があります。 CORS設定ミスが危険なのは、許可していない外部サイトのJavaScriptへ、機密情報を含むレスポンスを読ませるからです。 CORSはサーバがブラウザへクロスオリジンのレスポンス読み取りを許可する仕組みであり、認証、認可、CSRF対策の代わりにはなりません。 許可するオリジンをスキーム、ホスト、ポートの完全一致で管理し、nullオリジンやリクエストのOriginを無条件に許可する設定は避けます。 Originによってレスポンスを変える場合はVary: Originを返し、認証情報付きの読み取りを許すエンドポイントを必要最小限にします。

遷移先を入力値のまま受け入れると、正規ドメインの信用も攻撃に使われます。 オープンリダイレクトは、ログイン後の戻り先などを悪用し、利用者を偽サイトへ誘導する脆弱性です。 OAuthでも、redirect_uriの照合が不十分な場合や、登録済みの遷移先にオープンリダイレクトがある場合は、認可コードなどの漏洩へ連鎖します。 遷移先はサーバ側のIDから選ぶ設計を優先します。 同一サイト内のパスを受け取る場合も、固定したベースURLに対して標準のURLパーサで解決し、解決後のオリジンが完全一致することを確認して、//evil.exampleのようなネットワークパス参照を拒否します。 外部URLが必要なら、スキーム、ホスト、ポートを許可リストで完全一致させます。

配信層では、一人への不正なレスポンスが多数の利用者へ増幅されます。 Webキャッシュポイズニングは、キャッシュキーに含まれないヘッダやパラメータでレスポンスを変え、汚染した結果をCDNへ保存させる攻撃です。 レスポンスを左右する入力とキャッシュキーやVaryを一致させ、認証済みの応答や利用者ごとに変わる応答はprivateno-storeなどを使って共有キャッシュから外します。

配信層とアプリケーション層が、同じHTTPリクエストを異なる境界で区切る問題もあります。 HTTPリクエストスマグリングを防ぐには、CDN、ロードバランサ、Webサーバ、アプリケーションのHTTP解析を更新し、Content-LengthTransfer-Encodingの併記や矛盾する複数のContent-Lengthなど、メッセージ境界が曖昧なリクエストを入口で拒否して解釈を一致させます。 HTTP/2からHTTP/1.1へ変換する構成でも、禁止ヘッダ、疑似ヘッダ、本文長を検証し、中継点ごとのリクエスト境界が一致することをテストします。

これらの問題は、アプリケーションの入力検証だけでは防げません。 出力エンコーディング、Cookie属性、レスポンスヘッダ、遷移先、キャッシュキーまで含めて、ブラウザと配信層が付与する信頼を制御する必要があります。

外部入力が命令、構造、到達先に変わる攻撃面

「入力値を検証する」は正しい方針ですが、万能な検証関数はありません。 安全な文字列は、値を渡す先によって変わるからです。 メールアドレスとして正しい入力でも、SQLやシェル、テンプレートの構文へ連結すれば命令として解釈されます。

文字列を命令として実行させない

SQLインジェクションでは、入力がSQLの値ではなく構文として解釈されます。 防御の中心は、文字列を自前で無害化することではなく、パラメータ化クエリやORMの安全なAPIで「SQL」と「値」を分離することです。 テーブル名や並び順のようにプレースホルダを使えない要素は、許可した候補から選びます。

OSコマンドインジェクションも構造は同じです。 ファイル名やホスト名をシェルコマンドへ連結すると、区切り文字を使って別のコマンドを実行される可能性があります。 可能ならOSコマンド自体を使わず、必要な場合もシェルを介さずに実行ファイルと引数を分離し、実行ユーザーの権限を絞ります。 ただし、引数を分離しても--helpのような値がオプションとして解釈される引数インジェクションは残るため、実行ファイルと固定オプションをコード側で決め、可変引数を許可リストで検証します。

サーバサイドテンプレートインジェクションは、利用者が入力した文面をテンプレートの値ではなく、テンプレートそのものとして評価したときに起きます。 メールや帳票をカスタマイズできる機能でも、固定したテンプレートへ許可済みの値だけを渡す設計が基本です。 表現力の高いテンプレートエンジンを利用者へ開放すると、サーバ上のコード実行に届く場合があります。

三つに共通する対策は、エスケープ技法の暗記ではありません。 命令を固定し、外部入力は値として渡すことです。

構造化データの機能を必要な範囲へ閉じる

入力形式がXMLやJSONになっても、境界が消えるわけではありません。 パーサやオブジェクト変換には、アプリケーションが必要としていない機能まで含まれることがあります。

XML外部実体参照(XXE)は、XMLパーサの外部実体機能を通じて、サーバ内のファイルや内部URLを読み込ませる脆弱性です。 実体の再帰的な展開によって、メモリやCPUを枯渇させる場合もあります。 DTD、外部実体、外部DTDの読み込みを無効にし、不要なXIncludeや外部スキーマ取得も止め、入力サイズと展開量を制限して解析します。 XMLが要件でなければ、不要な機能を持たない形式へ置き換える判断も有効です。

プロトタイプ汚染は、JavaScriptで外部入力を再帰的にマージしたり動的なキーへ代入したりするとき、__proto__constructor.prototypeなどを経由してプロトタイプを書き換えられる問題です。 入力をスキーマで検証して未知の項目を拒否し、値を読むときはObject.hasOwn()などで自身のプロパティかを確認します。 任意キーの格納にはMapやプロトタイプを持たないオブジェクトを使います。

安全でないデシリアライゼーションでは、信頼できないデータから任意の型やオブジェクトグラフを復元する処理が、コード実行、アクセス制御の破壊、サービス停止につながります。 ネイティブなオブジェクト形式を外部境界で受け取らず、プリミティブな値からなるデータ交換形式を、固定したデータ転送用の型やスキーマへ変換します。 型情報を入力側に選ばせず、復元してよい型を処理前に限定します。 復元後の型検査では、デシリアライズ中に起きるコード実行を止められません。

いずれも「形式として正しく解析できた」だけでは安全になりません。 必要な機能と項目だけを許可し、パーサや変換処理の能力を狭める必要があります。

ファイルとネットワークの到達範囲を制限する

利用者が指定できる対象は、文字列だけではありません。 ファイル名、アップロード内容、取得先URLを受け取る機能では、サーバが持つファイル権限とネットワーク権限が攻撃者の手に渡り得ます。

パストラバーサルは、../などを含む入力で基準ディレクトリの外へ移動し、想定外のファイルを読み書きする脆弱性です。 入力から直接パスを組み立てず、内部IDから安全な保存先へ対応づける設計を優先します。 パスを受け取る必要がある場合はデコード処理を一か所に集約し、OSのパスAPIで正規化、実体化して、許可ディレクトリ内に収まることを文字列の前方一致ではなくパスの境界として確認します。 シンボリックリンクや検査後の差し替えも考慮し、可能ならリンクをたどらないAPIでファイルを開きます。 保存領域を別プロセスやコンテナへ分離し、ファイル権限を絞れば、検証をすり抜けた場合の影響も狭められます。

ファイルアップロードの検証不備では、拡張子やクライアント申告のMIMEタイプだけを信じると、実行可能なファイルや想定外の内容を保存されます。 許可する拡張子を業務上必要な形式へ限定し、クライアント申告のMIMEタイプは補助情報にとどめ、ファイルシグネチャと対象形式のパーサでも内容を検査します。 いずれか一つの検査だけで安全とは判断せず、可能な形式は再エンコードします。 ファイルはサーバが生成した名前で、アプリ本体とはオリジンを分けて認証Cookieを送らない専用ホスト、またはWebルート外かつ実行権限のない領域へ保存します。 サイズと個数の上限、アップロードとダウンロードの認可、必要に応じたマルウェア検査も同じ境界の一部です。 配信時は正しいContent-TypeX-Content-Type-Options: nosniffを設定し、インライン表示が不要な形式はContent-Disposition: attachmentで返します。

SSRF(サーバサイドリクエストフォージェリ)は、画像取得やURLプレビュー、Webhook確認などの機能を通じて、サーバから攻撃者が影響できる宛先へ通信させる脆弱性です。 攻撃者の端末からは届かない内部サービスやクラウドのメタデータサービスに対し、情報の読み取りだけでなく更新操作を実行される場合もあります。 通信先を固定できる機能では、許可するスキーム、ホスト、ポートを完全一致の許可リストへ限定します。 DNSから得たIPv4とIPv6のすべてのアドレスを確認し、検証した宛先と実際の接続先がずれない構成にします。 自動リダイレクトは原則として無効にし、必要な場合は移動先を各段階で同じ基準により再検証します。 アプリケーションからの外向き通信をネットワーク側で制限し、ループバック、プライベート、リンクローカル、メタデータサービスなどへの到達を必要な範囲に絞れば、入力検証をすり抜けた場合の影響も狭められます。

この領域では「不正な文字を除けば安全」という発想が通用しません。 安全なAPIを使い、不要な機能を無効にし、プロセスとネットワークの権限を絞ることで、攻撃者へ渡る能力そのものを小さくします。

認証とセッションにある攻撃面

ログインには、似ているようで異なる三つの判断があります。 アカウントに結び付いた認証要素を利用者が制御していることを確かめる判断が認証、確認済みの状態を引き継ぐ仕組みがセッション管理、その利用者に操作を許すかを決める判断が認可です。 一つの仕組みで三つを代用すると、ログイン済みであることが過剰な信用へ変わります。

パスワードを狙う攻撃と安全な保存

認証の入口では、パスワードが正しくても本人とは限りません。 クレデンシャルスタッフィングは、別サービスから流出したIDとパスワードを使い回し、自動化されたログインを試す攻撃です。 ブルートフォース攻撃は一つまたは少数のアカウントへ多数の候補を試し、パスワードスプレーは少数の使われやすいパスワードを多数のアカウントへ試します。 試行の向きが違うため、アカウント単位の固定ロックだけでは、分散した送信元や複数アカウントを横断する攻撃を捉えられません。

アカウント、IPアドレス、ネットワーク、端末など複数の単位で試行を制限し、複数アカウントにまたがる失敗と不審な成功を相関して検知します。 流出済みまたは使われやすいパスワードを登録時と変更時に拒否し、アカウントの存在を応答内容や処理時間から推測されにくくします。 多要素認証を導入し、対応できるサービスではパスキーやセキュリティキーなど、フィッシング耐性のある方式を優先します。 パスワードの長さ、ブロックリスト、試行制限の基準にはNIST SP 800-63Bを参照できます。

データベースが漏洩した後のオフライン推測には、ログイン画面の試行制限が届きません。 パスワードの安全な保存では、利用者ごとに異なるソルトと、Argon2idなどのパスワード保存用アルゴリズムを使い、本番環境の性能に合わせて計算コストを調整します。 レインボーテーブル攻撃は、ソルトのない高速なハッシュへ事前計算結果を使い回す攻撃です。 一意のソルトは事前計算の使い回しを防ぎますが、個々の弱いパスワードへの推測まで止めるわけではありません。

ソーシャルエンジニアリングは、偽サイトや電話などで利用者や担当者を動かし、認証情報や不正な操作を引き出します。 訓練だけへ責任を寄せず、フィッシング耐性のある認証、重要操作の再認証、別経路での確認、職務分離、迅速な報告とセッション失効を組み合わせます。

セッションとトークンを保護する

ログイン後の状態を識別するセッションIDは、それ自体が認証情報です。 セッションIDには、暗号学的に安全な乱数から生成した、意味を持たず推測できない値を使います。 セッション固定化は、攻撃者が知っているセッションIDを利用者へ使わせ、そのままログイン後の状態へ相乗りします。 ログイン成功時と権限が変わるときにセッションIDを再発行し、以前のIDを無効化すれば、ログイン前に固定された値を切り離せます。

一方、セッションハイジャックは、ログイン後のセッションIDを盗んで利用者になりすます攻撃です。 セッションCookieへHttpOnlySecure、適切なSameSite属性を設定し、DomainPathの範囲を必要最小限にします。 通信をTLSで暗号化し、サーバ側でアイドルタイムアウトと絶対タイムアウトを強制し、ログアウトや侵害時の失効手段を持たせます。 XSSを防ぐことも、ブラウザ上の認証済み状態を攻撃者に悪用させないための対策になります。

JWTは認証方式そのものではなく、クレームを署名または暗号化して運ぶトークン形式です。 JWTの検証不備では、署名を検証しない実装、受け入れるアルゴリズムを攻撃者に選ばせる実装、用途の異なるJWTを受け入れる実装などが問題になります。 JWT Best Current Practices(RFC 8725)に沿って、受け入れる署名アルゴリズムと鍵を発行者と用途ごとの許可リストで固定し、署名、必須クレーム、発行者、利用先、有効期限、利用開始時刻、期待するトークンの用途を検証します。 トークン内のjkux5uから鍵を無条件に取得せず、kidは信頼済みの鍵集合から鍵を選ぶためだけに使います。 一般的な署名付きJWTのペイロードはBase64urlでエンコードされるだけで暗号化されないため、読まれて困る情報を入れるべきではありません。 短い有効期限は漏洩したトークンを使える時間を減らしますが、即時失効の代わりにはなりません。 失効リスト、参照トークン、セッションのバージョン管理など、要件に合うサーバ側の失効機構も設計します。

OAuth、OpenID Connectと回復経路を閉じる

OAuthはAPIへのアクセスを委任する認可の枠組みであり、OpenID Connectはその上に利用者を認証する仕組みを追加します。 どちらも、外部サービスへ任せただけで実装上の検証が不要になるわけではありません。 OAuth 2.0 Security Best Current Practice(RFC 9700)に従い、リダイレクトURIは事前登録した値と完全一致で照合します。 ネイティブアプリのループバックIPリダイレクトURIでは、動的に割り当てるポート番号だけを例外として扱います。 認可コードフローでは、公開クライアントに必須、機密クライアントにも推奨されるPKCEを使い、チャレンジ方式はS256を選びます。 Implicit GrantとResource Owner Password Credentials Grantは、新しい実装では使いません。 認可応答のCSRF対策には、ブラウザのセッションへ安全に結び付けたstate、OpenID Connectのnonce、対応を確認済みのPKCEを、プロトコルの条件に合わせて使います。 複数の認可サーバを扱うクライアントでは、認可応答のissも検証し、別の認可サーバとの応答を取り違えるMix-Up攻撃を防ぎます。 OpenID ConnectのIDトークンでは署名、発行者、利用先、有効期限を検証し、認証要求でnonceを送った場合は同じ値であることを確認します。 APIが受け取るアクセストークンでは、トークン形式に応じて署名または有効性を確認し、発行者、利用先、スコープや権限を検証します。 IDトークンをAPIの認可に、アクセストークンをログインの証明に流用しません。 公開クライアントへリフレッシュトークンを発行する場合は、送信者制約かリフレッシュトークンローテーションを適用し、漏洩後の再利用を検知または防止します。 OpenID Connectの利用者は、変更され得るメールアドレスではなく、発行者を示すissと利用者を示すsubの組み合わせで識別します。 独自実装を避け、保守されているライブラリと認証サービスの標準機能を使う方が、検証の抜けを減らせます。

パスワードの保存、再設定、MFAの解除、メールアドレス変更も認証経路です。 本体のログインだけを強化しても、本人確認の弱い再設定画面やサポート窓口が残れば、攻撃者は弱い方から入ります。 回復コードと再設定トークンは短時間かつ一回限りにし、認証要素や連絡先の変更を本人へ通知し、高リスクな変更には再認証を要求します。

認可とAPIの入出力にある攻撃面

ログイン済みであることは、「このデータを扱ってよい」という証明にはなりません。 認可は画面の表示ではなく、サーバが受け取るすべての操作について確認する必要があります。

IDOR(安全でない直接オブジェクト参照)は、APIではBOLAとも呼ばれ、パス、クエリ、ヘッダ、本文などのオブジェクト識別子を操作して、許可されていない対象へアクセスできる脆弱性です。 データをIDだけで取得してから画面側で隠すのではなく、ログイン中の利用者や所属組織を起点に、許可された範囲から取得します。 推測しにくいIDは補助策にすぎないため、クライアントから識別子を受け取るすべての処理で、現在の主体がその対象へ要求された操作を行えるかを確認します。 一覧、詳細、更新、削除、ダウンロードでは必要な権限が異なり得るため、各操作に対応した認可が必要です。

権限昇格は、一般利用者が管理者向けの操作を実行するなど、役割の境界を越える問題です。 管理メニューを非表示にしても、URLやAPIを直接呼べるなら防御になりません。 認可処理を共通の層へ集約し、明示的に許可されない操作は拒否する設計にします。 権限変更や管理操作は監査ログにも残します。

認可は操作単位だけでなく、属性単位にも必要です。 マスアサインメントは、更新APIのリクエストへroleapprovedなどの項目を足し、本来変更できない属性まで一括更新する脆弱性です。 画面に表示した項目ではなく、役割と操作ごとに更新を許可する属性をサーバ側で列挙します。

同じ属性単位の認可不備がレスポンス側に現れる問題が、過剰なデータ露出です。 モデルをそのままJSONへ変換すると、画面では使っていないメールアドレス、権限情報、内部ID、認証関連の属性までレスポンスへ含まれる可能性があります。 返してよい属性をシリアライザやレスポンススキーマで明示し、利用者の役割ごとに分けます。

マスアサインメントは「受け取ってよい項目」の不備であり、過剰なデータ露出は「返してよい項目」の不備です。 APIのリクエストとレスポンスを契約として定義し、両方向を許可リストで閉じると、同じ設計原則で防げます。

この原則はREST APIだけに限りません。 GraphQLでは、ノード、エッジ、フィールドへどの問い合わせ経路から到達しても同じ認可規則を適用します。 WebSocketではWSSで通信を保護し、接続時にOriginを完全一致の許可リストで検証します。 ただし、Originの検証はブラウザからのクロスサイト接続を防ぐ対策であり、ヘッダを偽装できる非ブラウザクライアントの認証には使えません。 接続を認証済みの主体へ結び付けたうえで、メッセージごとの操作と対象にも認可を適用し、ログアウト、トークンの期限切れ、権限変更時には接続中の状態を更新または終了します。

状態と計算資源にある攻撃面

危険なリクエストが、いつも不正な文字列を含むとは限りません。 正規のリクエストを同時に送る、または正規の形式のまま計算量だけを増やすことで、システムの制約を壊せる場合があります。

レースコンディションは、残高確認と更新の間へ別の処理が割り込み、二重出金やクーポンの重複利用を成立させる脆弱性です。 アプリケーションで「確認してから更新する」だけでは、二つのリクエストが同じ確認結果を見て通過できます。 条件付きの原子的更新、データベースの一意制約、適切な分離レベルのトランザクションやロックを使い、競合しても不正な状態を保存できないようにします。 冪等キーは同じ論理操作の再送をまとめる仕組みであり、異なるキーを使った競合の防止にはなりません。 キーと処理結果を原子的に記録し、同じキーの再送には保存済みの結果を返します。 キーを認証主体と操作へ結び付け、同じキーで異なる入力が届いた場合は拒否します。

ReDoSは、バックトラッキング型の正規表現エンジンで、曖昧な繰り返しや重複する選択肢を持つパターンへ細工した入力を与え、CPUを長時間占有する攻撃です。 曖昧な繰り返しを含む正規表現を避け、入力長を制限し、利用できる環境では線形時間で評価するエンジンや実行時間の上限を使います。 正規表現以外のパーサや検索機能でも、入力サイズと計算量の上限を持つ考え方は同じです。

可用性を守るには、要求回数だけでなく、一回の要求サイズ、処理量、同時実行数にも上限が要ります。 GraphQLでは深さに加えて、フィールド数、リスト件数、エイリアス、バッチを含むクエリコストを制限し、WebSocketでは接続数、メッセージサイズ、送信頻度を制限します。 外部API呼び出し、メール、SMS送信には費用上限とアラートも設けます。 タイムアウト、利用者、テナント、IPアドレス、操作、システム全体の各単位でのレート制限とクォータ、キューの容量、バックプレッシャーを設けることで、資源枯渇の成立可能性と影響範囲を下げます。

業務ロジックも攻撃面です。 初回限定クーポン、招待、返金、パスワード再設定などは、一回のリクエストが正しくても、順序、回数、並行実行を変えると意図しない結果になることがあります。 仕様書とテストには正常系だけでなく、工程の省略、順序の入れ替え、二回実行、同時実行、途中失敗を含め、守るべき制約が最後まで維持されることを確認します。

開発、配備、運用にある攻撃面

本番の安全性は、アプリケーションコードだけでは決まりません。 同じコードでも、デバッグ機能が公開され、管理画面に認証がなく、ストレージが全公開なら、守りは成立しません。

セキュリティミスコンフィグは、サーバ、フレームワーク、クラウド、CDNなどの設定が安全側から外れたときに生まれます。 不要な機能とポートを閉じ、管理画面を限定し、本番のエラーに内部情報を出さず、セキュリティヘッダを標準化します。 設定をInfrastructure as Codeで管理し、レビューと自動検査を通せば、環境ごとの手作業によるずれを減らせます。

APIキーとシークレットの漏洩は、認証情報がGit履歴、フロントエンドのバンドル、ログ、CI、コンテナイメージなどへ混入する問題です。 ブラウザへ配信する値は利用者から読めるため、フロントエンドへ埋め込んだ秘密情報を隠すことはできません。 秘密情報はシークレット管理基盤から実行時に取得し、権限と有効期限を絞り、コミット前とCIで漏洩を検査します。 クラウドやCIが対応している場合は、長期間使う固定キーを置く代わりに、ワークロードのIDからジョブや処理ごとの短期認証情報を発行します。 漏れた場合はファイルを消すだけでは足りず、認証情報を失効してローテーションします。

依存コードはビルド時または実行時のプロセス権限で動き、CIは配備権限、署名鍵、秘密情報など本番へ届く強い権限を持つ場合があります。 npmのサプライチェーン攻撃対策で扱ったように、正規パッケージの新しいバージョンへ悪意あるコードが混入すると、公開済み脆弱性を探すスキャンだけでは間に合いません。 ロックファイルでバージョンと完全性情報を記録し、CIではその内容を変更しない固定インストールを強制します。 非緊急の更新には公開直後のバージョンを自動取得しない猶予期間を設け、インストール時スクリプトを必要なものだけに限定します。 既知の脆弱性を修正する更新は一律に待たず、変更内容を検証してリスクに応じた速度で適用します。 依存関係の棚卸しとSBOM、既知の脆弱性を探すSCA、成果物の署名、ビルドの来歴確認、CIの最小権限化を組み合わせます。 署名があることだけで安全とは判断せず、配備時に署名者、対象、来歴が自社の方針に合うかを検証します。 対象はnpmだけではなく、OS、言語ランタイム、コンテナイメージ、CIのアクション、ビルドツール、IDE拡張にも広がります。

暗号化も運用を含む仕組みです。 漏洩範囲を小さくするうえで直接的に効くのは、機密データを分類し、不要なデータを収集せず、保存期間を過ぎたデータを削除することです。 通信は証明書を正しく検証するTLSで保護し、必要な内部サービス間の通信も暗号化します。 パスワードは復号できない専用のハッシュ方式で保存し、保存データの暗号化は守る資産と想定する侵害経路に合わせて選びます。 暗号処理には保守されている実装を使い、改ざんも検知できる認証付き暗号と、鍵やトークンを生成する暗号学的に安全な乱数生成器を用途に応じて使います。 鍵はKMSやVaultなどで管理し、生成、利用権限、ローテーション、失効、破棄のライフサイクルを定め、暗号化対象のデータとはアクセス権限を分離します。 URLと共有キャッシュには秘密情報を含めず、ログにも原則として記録しません。 ブラウザでセッションを管理できる構成では、認証トークンをlocalStoragesessionStorageへ永続化せず、HttpOnlyを設定したCookieなど、JavaScriptから読み取れない保管方法を優先します。

ただし、「暗号化されていれば認可は不要」を意味しません。 TLSは通信途中の盗聴を防ぎますが、ログイン中の利用者へ他人のデータを返すIDORまでは防げないからです。

ログ、アラート、例外処理で被害を止める

すべての侵入を入口で止められる前提には立てません。 未知の脆弱性、委託先の侵害、盗まれた正規アカウントなど、既存の防御を通過する経路が残るからです。 通過された後に気づき、影響範囲を特定し、認証情報や処理を止められる設計が必要です。

OWASP Top 10:2025でも、アクセス制御やインジェクションと並んで、セキュリティログとアラートの不備、例外的な状況への不適切な対応が別個のカテゴリとして扱われています。 ログを保存するだけでは検知になりません。 異常なログイン、MFAや権限の変更、大量のデータ取得、管理操作、秘密情報や設定の変更、外向き通信の急増を、しきい値と責任者を決めたアラートへつなぎます。

一方、パスワード、セッションID、アクセストークン、カード情報をログへ出すと、監視基盤が新しい漏洩経路になります。 調査に必要な利用者、操作、対象、結果、時刻、追跡IDを残しつつ、秘密情報は原則として記録せず、不要な個人情報は除外し、必要な個人情報も用途に応じてマスキングします。 時刻を同期し、構造化したログを追跡IDで相関できるようにして、ログイン、認可、入力検証の失敗も記録します。 外部入力をログへ含める場合は、改行や区切り文字を無害化し、長さを制限してログインジェクションと容量枯渇を防ぎます。 アプリケーションが侵害されても消されにくい別系統へログを転送し、保存先では改ざんと削除を制限します。 ログの閲覧権限とアクセス監査、保存期間、期限後の廃棄手順を定め、転送や保存の停止も検知します。

認証、認可、署名検証などのセキュリティ判断は、依存先の障害や例外が起きても未確認のまま許可しない設計にします。 認可サービスへ到達できないときに処理を許可する、署名検証で例外が出たトークンを通す、タイムアウトした決済を無条件に再実行する、といった、安全側に倒れない例外処理は避けます。 可用性が必要な機能は、機密性や完全性を損なわない範囲へ安全に縮退させます。 エラーは利用者へ内部のスタックトレースやSQLを見せず、サーバ側では調査できる情報を保持します。 再試行には回数と間隔を設け、二重実行を防ぐ冪等性も必要です。 多段処理の途中で失敗した場合は、同じトランザクション内の変更を安全な状態へロールバックします。 外部サービスへの送信など取り消せない副作用には、補償処理、照合、手動復旧の状態をあらかじめ設計し、接続、ファイル、ロックなどの資源を確実に解放します。 外部サービスの障害にはタイムアウトとサーキットブレーカーを設けます。

インシデント対応は、事故が起きてから順番を考えると遅れます。 セッションとトークンの失効、APIキーのローテーション、汚染キャッシュの削除、侵害された成果物の隔離、ログの保全、関係者への連絡を手順化し、定期的に演習します。 対応手順には重大度、指揮者と決裁権限、エスカレーション条件、契約や法令に応じた通知判断の経路も定めます。 対応時系列と証拠を保全し、事後レビューの結果を脅威モデル、検知ルール、対応手順へ反映します。 バックアップは本番の認証情報や削除権限から分離し、改ざんされていない時点へ実際に復元できることを定期的に試します。 復旧できることは、可用性だけでなく被害の上限を決める防御です。

設計から運用まで防御をつなぐ

脆弱性診断を公開直前に一度実施するだけでは、継続的に変わるWebシステムを守れません。 新しいAPI、権限、依存パッケージ、外部連携が追加されるたびに、攻撃面も変わります。

セキュリティは、開発工程ごとに次の成果物へ落とします。

工程作るもの確認すること
企画と設計資産一覧、データフロー、信頼境界、脅威モデル何を守るか、誰を信頼するか、侵害時にどこまで届くか
実装安全なAPI、共通認可、入力と出力のスキーマ標準の防御を外していないか、最小権限になっているか
レビューとテスト単体テスト、結合テスト、静的解析、動的診断、依存関係検査拒否すべき入力、他人の権限、同時実行、異常系を試しているか
配備再現可能な設定、管理されたビルド環境、署名と来歴を持つ成果物、秘密情報の注入ダイジェスト、署名者、ビルダー、ソース、来歴を検証し、開発用設定や不要な権限を本番へ持ち込んでいないか
運用ログ、アラート、更新手順、対応手順、バックアップ攻撃と異常を検知し、止め、復旧できるか

NIST Secure Software Development Frameworkも、特定の開発手法を置き換えるのではなく、既存の開発ライフサイクルへ安全な開発慣行を組み込む枠組みです。

OWASP Top 10は、主要なリスクを組織内で共有する入口として使えます。 ただし、Top 10に該当する問題がないことだけで、アプリケーション全体の安全性を保証する資料ではありません。

具体的なセキュリティ要件にはOWASP Application Security Verification Standard 5.0.0を使い、テスト項目の設計には現行の安定版であるOWASP Web Security Testing Guide 4.2を使うと、担当者の経験だけに依存しにくくなります。 自社の要件、実装、テスト結果を同じ基準へ対応づけることで、「何を確認したのか」を説明できる状態にします。

生成AIやコーディングエージェントを使っても、この構造は変わりません。 生成速度が上がるほど、人間が目視できる変更量を超えやすくなるため、認可テスト、依存関係検査、シークレット検査、配備前の品質ゲートをコードと同じ速度で回せる必要があります。 リポジトリ、Issue、ログ、Webページには、コーディングエージェントの挙動を変える間接的なプロンプトインジェクションが含まれ得ます。 OWASP Secure Coding with AI Cheat Sheetが示すように、エージェントは隔離環境で動かし、読めるファイル、実行できるコマンド、接続先を作業ごとに絞ります。 本番の秘密情報や顧客データを渡さず、必要な認証情報は作業単位の短期権限にし、配備や権限変更など影響の大きい操作には人間の承認を残します。 エージェントが実行したツール、対象、結果、承認記録を監査できる形で残します。 生成物も人間が書いたコードと同じ基準で検証します。

何から対策するか

31の攻撃と脆弱性に横断対策を加えて一度に並べると、「全部を今すぐ直すしかない」と見えるかもしれません。 しかし、予算と開発時間が有限である以上、優先順位のない全件対応は続きません。

最初に、外部公開しているドメイン、API、管理画面、ストレージ、クラウド資産を棚卸しします。 次に、それぞれが扱う個人情報、決済情報、認証情報、事業上の重要データと、侵害された場合の影響範囲を対応づけます。 存在を把握していない資産は、診断にも更新にも含められません。

個々のリスクの大きさは、次の軸で比較します。

  • 到達可能性:インターネットから直接届くか、認証が必要か、内部からだけ届くか
  • 悪用のしやすさ:既知の手順で再現できるか、特殊な条件が必要か
  • 実悪用の確認:公開済み脆弱性では、攻撃が実環境で観測され、ベンダーやCISA Known Exploited Vulnerabilities Catalogで報告されているか
  • 影響度:機密性、完全性、可用性のどれを、どの範囲で失うか
  • 連鎖性:一つの侵害から管理権限、秘密情報、他システムへ広がるか
  • 検知と復旧:攻撃に気づけるか、失効、切り戻し、復元ができるか

対応の順序を決める段階では、修正と運用にかかる費用、利用者や事業への影響、代替策の有無も別の軸で比較します。 ただし、対策費用が高いことは、リスクが小さいことを意味しません。

本来限定すべき管理画面がMFAやアクセス制限なしで公開されている、現在も有効な秘密情報が漏洩している、署名を検証しない認証がある、認可なしで個人情報を取得できるといった、悪用可能で影響の大きい問題は即時対応です。 一方、発生条件が限定され影響も小さい問題は、期限と責任者を決めて計画的に扱えます。 判断を開発者の頭の中へ残さず、リスクとして記録し、事業責任者と共有することが前提です。

対応には四つの選択肢があります。 不要な個人情報を保持しない、危険な機能を廃止する回避、認可や入力制御を追加する低減、保険や契約で損失や責任の一部を分担する移転、残るリスクを決裁と記録のうえで受け入れる保有です。 四つの対応は排他的ではなく、一つのリスクへ組み合わせて使います。 決済や認証を専門サービスへ委ねることは実装リスクの低減や一部の移転になりますが、委託先の選定、設定、責任分界、事故時の顧客対応まで消えるわけではありません。 リスクを保有する場合も、責任者、残存リスク、判断根拠、監視方法、見直し期限を記録します。

迷ったときは、個別の脆弱性より広く効く基盤から整えます。 資産台帳、管理者のMFA、共通認可、シークレット管理、依存関係の更新、ログとアラート、バックアップと復旧手順は、複数の攻撃に同時に効くからです。

自社の現在地を確認する15項目

次の十五項目のうち、答えが曖昧なものは、そのまま優先調査の候補になります。

  1. 外部公開しているWebサイト、API、管理画面、ストレージを一覧化しているか
  2. 各システムが保持する個人情報、決済情報、認証情報と保存期間を把握しているか
  3. 管理者と開発者のアカウントへ多要素認証を適用し、可能な入口ではフィッシング耐性のある方式を使っているか
  4. パスワードを専用アルゴリズムで保存し、再設定やMFA解除を本体のログインと同等以上に保護しているか
  5. セッションとトークンに安全な保管方法、有効期限、失効手段があるか
  6. 認可が必要なすべてのAPIで、サーバ側の認可を操作、対象、属性へ適用しているか
  7. リクエストとレスポンスの項目を許可リストで定義しているか
  8. SQL、テンプレート、OSコマンドへ入力を文字列連結していないか
  9. ファイルアップロードとサーバからの外向き通信に、形式、容量、宛先の制限があるか
  10. 依存パッケージ、ビルドツール、実行環境を棚卸しし、固定、検査、更新、来歴確認を行っているか
  11. 秘密情報をコードとログの外で管理し、失効とローテーションができるか
  12. 本番設定をコードとレビューで管理し、不要な機能、公開範囲、権限を継続的に検査しているか
  13. 認証失敗、権限変更、大量取得、設定変更を検知するアラートと対応責任者がいるか
  14. インシデント対応手順があり、分離したバックアップからの復元を実際に試しているか
  15. 設計、実装、配備、運用の各段階でセキュリティ要件を検証しているか

十五項目すべてに「はい」と答えられても、脆弱性がゼロになるわけではありません。 しかし、どこを信頼し、何を検証し、破られた後にどう止めるかを説明できる組織は、未知の攻撃にも対応しやすくなります。

まとめ

  • Webセキュリティは信頼境界の設計です:攻撃面をブラウザ、入力、認証、認可、状態、運用の六つに分けると、境界の見落としと共通対策が見えます
  • 入力検証だけでは守れません:安全なAPI、サーバ側認可、最小権限、出力制御、資源制限を重ねます
  • コードの外も攻撃面です:依存パッケージ、CI、設定、秘密情報、ログ、例外処理まで同じシステムとして扱います
  • 診断は開発工程へ組み込みます:設計時の脅威モデルから、実装、テスト、配備、監視、復旧まで防御をつなぎます
  • 優先順位は事業リスクで決めます:資産、到達可能性、影響範囲、連鎖性、復旧可能性を比較し、判断を記録します

次に新しい脆弱性の名前を聞いたときは、名称だけを覚える必要はありません。 どの信頼境界が破られ、何の権限が攻撃者へ渡り、どの防御と検知で止めるのかを確認してください。 その問いに答えられることが、現代Webセキュリティの全体像を理解している状態です。

この記事をシェア