Web脆弱性の防ぎ方
一つの入力・Request経路について、信頼できない入力、影響を受ける処理、防御層、確認方法を整理します。Injection、XSS、CSRF、SSRF、Path・File処理とserver-side authorizationの境界を空欄確認票へ落とします。
Web脆弱性を防ぐには、攻撃名を暗記するより、一つの入力がどの処理へ届くかを追う方が実装につながります。確認するのは「何を信頼しないか」「どの処理が影響を受けるか」「どの層で防ぐか」「何を証拠にするか」の四点です。
この記事は、初めてWeb機能を実装する人が、一つの入力・Request経路を設計、実装、testへ渡せる状態を目指します。Requestとは、browserや別のsystemからserverへ届く要求です。endpoint、form、file upload、外部URL取得など、対象を一つ選んで読み進めます。
完成時には、空欄の「一つの入力・Request経路のSecurity確認票」が埋まり、次の状態を判断できます。
- 設計へ戻す
- 実装へ渡す
- testへ渡す
- HOLDにして担当者と再開条件を決める
確認票が埋まっても、脆弱性がないことの証明にはなりません。NISTのSecure Software Development Framework(SSDF)も、secure development practiceを脆弱性とその影響を減らすための枠組みとしており、無欠陥を保証していません(NIST SP 800-218 v1.1)。
最初に固定すること
対象にするのは、機能全体ではなく一つの経路です。たとえば「Request bodyを受け、databaseを更新し、HTMLを返す」までを一件にします。入口と終点を同時に複数選ぶと、未確認の分岐が見えにくくなります。
開始前に、次の情報をそろえます。
- 機能の目的と、変えてよい状態
- endpoint、HTTP method、呼出元
- 入力項目と、値を変更できる主体
- database、OS process、browser、外部network、filesystemなどの処理先
- 実装に使うframework、library、runtimeの版
- 確認できる環境、担当者、対象版
機能の目的と受入条件が決まっていなければ、先に要件を受入条件まで落とすへ戻ります。security上の応答時間、記録、可用性などを数値と条件へ分ける場合は、非機能要件の決め方で扱います。
この記事で扱うのは、SQL・OS Command等のInjection、XSS、CSRF、SSRFを中心とした実装防御です。Path Traversal、危険なfile処理、server-side authorizationとの境界も含めます。一方、脅威モデル全般、認証・認可方式の詳細、暗号、supply chain、Cloud設定、脆弱性診断、penetration test、Tool比較は扱いません。攻撃payloadや未許可の攻撃手順も載せません。
一つの経路を四つの軸で見る
入力は、画面のtext fieldだけではありません。URL path、query、body、header、cookie、file名とfile本体、外部APIのresponse、利用者が以前保存したdatabase上の値も、変更できる主体と利用目的を確かめるまでは信頼済みにしません。
処理先は、その値を命令や構造として解釈する場所です。databaseのSQL interpreter、OSのprocess実行、browserのHTML・JavaScript解釈、serverのHTTP client、filesystemやfile parserが該当します。この記事では、これらを「影響を受ける処理」と呼びます。
| 軸 | 記録すること | 不十分な記録 |
|---|---|---|
| 信頼できない入力 | 入口、項目、変更できる主体、保存・変換の経路 | 「利用者入力」だけ |
| 影響を受ける処理 | SQL、command、HTML、outbound request、path、file処理、状態変更 | 「backendで使う」だけ |
| 防御する層 | codeとdataの分離、許可値、出力context、Request意図、到達先、認可 | 「sanitizeする」だけ |
| 確認 | 対象版、許可・拒否case、実行結果、review記録 | 「test済み」だけ |
経路は次の順で追います。
- 入口から値を一つ選ぶ
- decode、parse、型変換、保存、再読込みを順に書く
- 値が命令や構造として解釈される処理を探す
- その処理に合う防御を、server側または実行基盤へ置く
- 正常caseと拒否caseを、許可された環境で確認する
- 対象版、結果、未確認、次の担当を残す
入力検証だけを全脆弱性の共通解にしません。形式が正しくても、SQLの構造へ連結すればInjectionになり得ます。安全にHTMLへ表示できても、その利用者が対象resourceを更新してよいとは限りません。防御は、値が最終的にどう解釈されるかに合わせます。
SQLとOS commandはcodeとdataを分ける
SQL InjectionとOS Command Injectionは、外部入力が命令の一部として解釈される点が共通します。ただし、防御に使うAPIと確認対象は異なります。
SQLは値をparameterとして渡す
SQLの値は、prepared statement等のparameterized query(SQLのひな形と値を別々に渡す方法)でSQL codeと分けます。文字列を検証した後にqueryへ連結する方法は、値とcodeの境界を保証しません(OWASP SQL Injection Prevention Cheat Sheet)。
table名、column名、sort directionのように値parameterへbindできない構造は、外部入力をそのまま使いません。不要なら動的に選ぶ設計をやめます。必要な場合は、外部の選択値をcode内の既知のtable名や並び順へmappingします。
stored procedureも、使うだけで安全になるわけではありません。procedure内部でdynamic SQLを安全でない方法で組み立てていないか、parameterを値として扱っているかまで確認します。
確認では、対象版のdata access codeを読み、外部入力が文字列結合でquery構造へ入っていないことを確かめます。正常な値が保存・検索できるcaseと、許可値にない構造選択を拒否するcaseを、許可されたtest環境で実行します。利用中のdriverやORMで安全なAPIを公式資料から確認できなければHOLDです。
OS commandは呼出し自体を減らす
OS commandは、まずshellを呼ばず、同じ処理を行うlanguageまたはlibrary APIへ置き換えられるかを確認します。OWASPは、OS commandを直接呼ばないことを第一の防御にしています(OWASP OS Command Injection Defense Cheat Sheet)。
避けられない場合は、実行するcommandを固定し、commandとargumentを構造的に分けます。argumentは業務上必要な許可値へ限定し、processの実行権限も必要最小限にします。escapingだけでは、命令追加を防げても、許可したcommandの危険なargument利用まで防げない場合があります。
確認するのは、shellを介していないこと、実行対象が固定されていること、許可していないargumentをserver側で拒否すること、runtimeの実行userと権限が記録されていることです。任意のcommandやargumentを受け入れる必要があるのに、影響範囲と責任者を決められない場合は実装へ進めません。
XSSは出力先で防ぐ
XSSでは、値を受け取った場所ではなく、browserが値を解釈する場所を確認します。同じ文字列でも、HTML本文、attribute、URL、JavaScript、CSSでは安全な表現が異なります。
基本は、利用frameworkの自動escapeを保ったまま、安全な出力APIを使うことです。自分で出力する場合は、値が置かれるcontextに対応したoutput encoding(browserが値を命令として解釈しない表現へ変換する処理)を使います。raw HTML、直接DOMを書き換えるAPI、escapeを無効にする機能は、通常の自動防御から外れるため、使用箇所と理由を個別に確認します(OWASP Cross Site Scripting Prevention Cheat Sheet)。
| 出力先 | 主な防御 | 確認すること |
|---|---|---|
| HTML本文・attribute | contextに合う自動escapeまたはencoding | templateのescapeが有効か |
| URLとして出力 | 許可するprotocol・destinationとURL contextのencoding | URLを作る層と表示する層を分けたか |
| JavaScript・CSS context | 可能なら外部入力を直接置かない。必要なら専用の安全なAPI | HTML用escapeを流用していないか |
| rich text | 要件に合う保守されたHTML sanitizerと出力規則 | 許可するelement・attributeのownerがいるか |
Content Security Policy(CSP)は追加防御になりますが、context別encodingや安全な出力APIの代替ではありません。CSPがあることだけをXSS対策の完了条件にしません。
確認では、外部入力が表示される全context、escapeを外す箇所、client-side rendering後のDOM操作を対象版で追います。許可されたtest dataがtextとして表示され、markupやscriptとして解釈されないことを確認します。rich textの許可範囲やsanitizerを決められない場合はHOLDです。
CSRFはRequestの意図をserverで確かめる
CSRFは、browserがcookie等のcredentialを自動送信する性質を利用し、利用者が意図していないstate-changing Requestを成立させる問題です。state-changingとは、登録、更新、削除、送信などserver側の状態や外部作用を変える処理です。
frameworkにCSRF防御がある場合は、その公式手順を有効にします。自前で扱う場合は、session構成に合うtoken patternを選び、tokenをbackendで検証します。状態を変える処理をGET等のsafe HTTP methodへ置かないことも前提です。OWASPはtoken、SameSite、Origin・Fetch Metadata等を成立条件に応じて組み合わせるよう示しています(OWASP Cross-Site Request Forgery Prevention Cheat Sheet)。
SameSite cookie属性は追加防御です。same-siteとsame-originは同じ範囲ではなく、navigationやHTTP method、browserの条件もあります。限定されたcaseを除き、SameSiteだけで完了としません。XSSが成立すると多くのCSRF防御が迂回され得るため、XSSとCSRFの確認は代替関係にもなりません。
Fetch Metadataを使うと、serverはSec-Fetch-SiteからRequest initiatorとtargetの関係をsame-origin、same-site、cross-site、noneとして受け取れます。redirect chainにcross-siteが含まれる場合の扱いも定義されています。ただし、これは2025年4月1日版のWorking Draftであり、完成したW3C Recommendationではありません(W3C Fetch Metadata Request Headers)。headerがないclientのfallbackとしてOrigin等を検証し、段階的に導入します(OWASP Cross-Site Request Forgery Prevention Cheat Sheet)。
確認するのは、state-changing endpointの一覧、tokenがない・一致しないRequestのserver-side拒否、SameSite設定、Fetch Metadata policyとfallback、XSS防御との対応です。認証・session構成に適したtoken patternを公式資料から判断できなければHOLDにします。
SSRFは接続先とnetwork経路を制約する
SSRFは、applicationが外部入力の影響を受けてserver-side requestを送り、意図しない内部service、外部service、server自身の資源へ到達する問題です。HTTPだけでなく、利用clientが扱うprotocolと実際の到達先を確認します。
接続先が既知なら、信頼するdestinationをallowlist(許可する候補だけを並べた一覧)にします。protocol、IP addressまたはdomainを利用libraryで正規化・検証し、不要なredirectを無効にします。URL文字列の見た目だけではなく、parserの解釈、DNS解決、redirect後の到達先までが対象です(OWASP Server Side Request Forgery Prevention Cheat Sheet)。
application層だけで終わらせず、serverから許可したdestinationとportだけへ到達できるよう、network route、firewall、proxy等でegressを制約します。network制約だけでapplicationのURL検証を省くこともできません。
任意の外部destinationへ接続する機能では、固定allowlistを使えない場合があります。この場合は、受け入れられるprotocol、public・private networkの境界、DNS・redirect、egress policy、timeout・response size等をsecurityとnetworkの担当者へ渡します。防御と安全な検証環境を決められるまでHOLDです。
確認は、許可された管理下のdestinationへ接続でき、許可していない分類のdestinationがapplicationとnetworkの両方で拒否されることを、承認済み環境で行います。実在する第三者や未許可systemをtest対象にしません。
Pathとfileは受入・保存・処理を分ける
Path Traversalでは、外部入力から作ったpathが、許可したbase directoryの外へ解決されないことを確認します。可能ならfile path自体を受け取らず、外部のfile IDをcode内の既知のpathへmappingします。
pathを組み立てる必要がある場合は、入力を一度だけdecodeした後、OSが解釈する一つのpath表現へ正規化(canonicalize)し、その結果が許可したbase directory内に収まることをserver側で検証します。特定文字の除去だけに頼らず、OSのpath表現、symbolic link、利用するpath APIも確認します(MITRE CWE-22, CWE 4.20)。
file uploadは、受入、保存、処理、配信を分けます。
| 工程 | 防御 | 単独では保証しないもの |
|---|---|---|
| 受入 | 必要なextensionのallowlist、size、file signature、upload権限 | clientのContent-Type、signature一致 |
| 名前 | applicationが生成した名前、長さと文字の制限 | 利用者が送ったfile名の置換だけ |
| 保存 | webroot外または別host、必要最小限の権限 | 非公開場所へ置いたことだけ |
| 処理 | 保守されたparser、resource limit、必要に応じたscan・sandbox・Content Disarm and Reconstruction(CDR。危険な要素を除いてfileを再構成する処理) | scannerの合格だけ |
| 配信 | server-side authorization、安全なContent-Typeとdisposition | URLを推測しにくくしたことだけ |
OWASPも、extension、Content-Type、signature、filename、size、authorization、保存場所、scan等を重ねるよう示しています(OWASP File Upload Cheat Sheet)。受け入れるfile形式と、その形式を開くparserのownerを決められない場合はHOLDです。
確認では、許可形式と上限内のfileを扱えるcase、許可しない形式・size・権限を拒否するcase、生成名と保存先、配信時のauthorization、scan結果だけに依存していないことを記録します。実際の危険fileを用意する手順は本記事では扱いません。
入力防御とauthorizationを分ける
入力防御とauthorizationは、答える質問が違います。
| 境界 | 答える質問 | server側の確認 |
|---|---|---|
| 入力・出力防御 | 値を対象処理へ安全に渡せるか | parameter、allowlist、encoding、destination、path等 |
| authentication | 呼出元は誰か | 認証方式の設計で扱う |
| authorization | そのactorが、そのresourceへ、そのoperationを行ってよいか | deny by defaultでevery requestを検証 |
形式が正しいresource IDでも、別の利用者のresourceを読んでよいとは限りません。buttonやlinkを画面から隠しても、server endpointのauthorizationにはなりません。OWASPは、permissionをdeny by defaultとし、every requestで検証し、authorization logicにunit・integration testを用意するよう示しています(OWASP Authorization Cheat Sheet)。
確認票にはactor、resource、operationを別々に書きます。許可される組合せだけでなく、別actor、別resource、許可されないoperationがserver側で拒否されるcaseもtestへ渡します。認証根拠、リソース単位の許可、セッション、拒否条件を別の設計へまとめる手順は、認証と認可の判断表で扱います。
一つの入力・Request経路のSecurity確認票
この確認票は、OWASP、CWE、NIST、W3Cの書式ではありません。公式資料の防御条件を、一経路の設計・実装・testへ引き渡すために本記事で整理した空欄成果物です。
# 一つの入力・Request経路のSecurity確認票
## 1. 対象
- 機能・経路名:
- 対象endpoint / job / event:
- 対象version・commit:
- owner:
- 確認日:
- 状態: [設計へ戻す / 実装へ渡す / testへ渡す / HOLD]
## 2. 入口と入力
- 呼出元:
- HTTP methodまたは起動条件:
- credentialが自動送信されるか:
- 信頼できない入力項目:
- 各項目を変更できる主体:
- 保存済み値・外部responseを再利用する箇所:
- decode・parse・型変換・canonicalizeの順序:
## 3. 影響を受ける処理
| 入力 | 変換後の値 | 影響処理 | 命令・構造として解釈される箇所 | owner |
|---|---|---|---|---|
| | | [SQL / OS command / HTML・DOM / state change / outbound request / path・file / resource操作 / その他] | | |
## 4. 防御
| 影響処理 | 主防御 | 追加防御 | server・network側の実装箇所 | 公式資料・対象version |
|---|---|---|---|---|
| | | | | |
- actor:
- resource:
- operation:
- authorizationをevery requestで検証する箇所:
- deny by defaultの扱い:
- error時に外部へ返す情報:
- logへ残す情報と、残さない秘密値・個人情報:
## 5. 確認と証拠
| Case | 前提・入力分類 | 期待結果 | 確認層 | 結果・証拠ref |
|---|---|---|---|---|
| 正常 | | | [unit / integration / review / runtime policy] | |
| 拒否 | | | | |
| authorization拒否 | | | | |
| dependency・network・parser失敗 | | | | |
- 確認環境と許可範囲:
- 未実行の確認:
- 結果が適用できる範囲:
- 対策後にも残る影響の可能性:
## 6. HOLDとhandoff
| 不明・未確認 | 影響 | owner | 再開条件 | 期限 |
|---|---|---|---|---|
| | | | | |
- 設計へ渡す境界・制約:
- 実装へ渡すAPI・設定・権限制約:
- testへ渡す許可case・拒否case・証拠:
- security担当へ渡す条件:
- 次に判断する人:
HOLDにする条件
次のいずれかが結論を変える場合は、空欄を推測で埋めずHOLDにします。
- 入力がどの処理へ届くか追えない
- 利用するframeworkやlibraryの安全なAPIを公式資料で確認できない
- 外部入力がSQL構造、command、HTML、URL、pathへ任意に入る
- state-changing Requestに、session構成と合うCSRF防御がない
- SSRFの実到達先またはnetwork egressを制約できない
- rich textやfile parserの許可範囲、更新owner、resource limitがない
- server-side authorizationのactor・resource・operationを決められない
- test対象、許可範囲、環境、復旧方法を確認できない
- 秘密情報、個人情報、顧客dataを安全に扱えるか判断できない
HOLDは中止ではありません。不明点、影響、確認する人、再開条件をそろえ、判断できる地点へ戻す状態です。
設計・実装・testへ渡す
設計には、入力から影響処理までの境界、許可する値と到達先、失敗時の扱い、authorizationのactor・resource・operationを渡します。実装には、安全なAPI、外部入力を渡さない構造、runtime権限、network・storage制約、対象versionを渡します。
testには、正常caseだけでなく、許可値にない入力分類、token不一致、許可しないdestination、base directory外へ解決されるpath分類、権限のないactor等の拒否caseを渡します。実用的な攻撃payloadを共有する必要はありません。具体的なtest levelと組合せはテストの種類と選び方で決めます。
一件の実装へ分けるときはAIに渡す実装タスクの切り方、変更後の対象版と証拠を確認するときはAI生成コードをレビューするへつなぎます。NIST SSDFは、secure codingに加え、人が読めるcodeのreview・analysisとexecutable codeのtestについて、scope、結果、issueの扱いを記録するよう示しています(NIST SP 800-218 v1.1)。確認票、code、test結果の対象versionを一致させます。
出典
- OWASP Foundation, SQL Injection Prevention Cheat Sheet(2026年8月20日確認)
- OWASP Foundation, OS Command Injection Defense Cheat Sheet(2026年8月20日確認)
- OWASP Foundation, Cross Site Scripting Prevention Cheat Sheet(2026年8月20日確認)
- OWASP Foundation, Cross-Site Request Forgery Prevention Cheat Sheet(2026年8月20日確認)
- OWASP Foundation, Server Side Request Forgery Prevention Cheat Sheet(2026年8月20日確認)
- OWASP Foundation, File Upload Cheat Sheet(2026年8月20日確認)
- OWASP Foundation, Authorization Cheat Sheet(2026年8月20日確認)
- MITRE CWE Program, CWE-22: Improper Limitation of a Pathname to a Restricted Directory(CWE 4.20)(2026年4月30日更新、2026年8月20日確認)
- National Institute of Standards and Technology, Secure Software Development Framework Version 1.1, NIST SP 800-218(2022年2月公開、2026年8月20日確認。2026年8月20日時点のFinal。Version 1.2はInitial Public Draft)
- World Wide Web Consortium, Fetch Metadata Request Headers(2025年4月1日Working Draft、2026年8月20日確認)
