入力バリデーションと出力エスケープの原則。入口で検証し、出口で文脈別にエスケープする
対象の目安: Webアプリ開発者 / 入門〜実務

「ユーザーの入力をきちんと検証していれば脆弱性は防げる」という考えは、半分だけ正しく、半分は危ない誤解を含んでいます。たしかに入力の検証(入力バリデーション)はセキュアコーディングの基本であり、不正なデータを入口で弾くことには大きな意味があります。しかし、XSSやSQLインジェクションといった代表的な脆弱性の「根本対策」は、入口の検証ではなく、データを使う出口での処理、すなわち出力エスケープ(あるいはエスケープに相当する安全なAPIの利用)にあります。この二つを混同したまま「入力をフィルタしているから安全だ」と考えると、思わぬ穴が残ります。
この記事では、入力バリデーションと出力エスケープという、よく一緒に語られながら役割がまったく異なる二つの対策を、それぞれが何のために存在するのかという原理から整理します。なぜ入口の検証だけでは攻撃を防ぎきれないのか、なぜ出口の処理が「文脈(コンテキスト)」に依存するのか、そして実務でどちらをどう使い分ければよいのかを、具体例と判断基準まで掘り下げます。XSSやSQLインジェクションそのものの仕組みは個別記事に譲り、ここでは両者に共通する「設計の土台」に焦点を当てます。
結論を先に言えば、入口の検証は「想定外のデータを早めに拒否する防御の一段目」、出口のエスケープは「データをコードとして誤解させないための根本対策」です。この役割分担を最初に頭に入れておくと、以降の話が一本の線でつながります。
入口と出口、役割の違い 早見表
まず全体像をつかむため、二つの対策の違いを一覧で整理します。詳細は後続のセクションで掘り下げます。
| 観点 | 入力バリデーション(入口) | 出力エスケープ(出口) |
|---|---|---|
| 目的 | 想定外・不正な形式のデータを早期に拒否する | データがコードとして解釈されるのを防ぐ |
| 実行する場所 | データを受け取った直後(リクエスト処理の入口) | データを別の文脈へ出力する直前 |
| 防げる主な問題 | 業務ロジックの破綻、過大な値、明らかな不正値 | XSS、SQLインジェクション等のインジェクション |
| 守りの位置づけ | 防御の一段目(多層防御の補助) | 根本対策 |
| 判断の軸 | 許可リスト(何を通すか)で組む | 出力先の文脈(どこへ出すか)で方式を選ぶ |
| 一方だけでは | 自由記述などを通す必要がある場面で破られる | 入力の業務的な妥当性は担保できない |
重要なのは、この二つが「二者択一」ではなく「両方必要」である点です。OWASPのInput Validation Cheat Sheetは、入力検証と出力エンコードはどちらも多層防御の一部として実装すべきで、片方だけでは不十分だと明言しています。
入力バリデーションとは何か、何を守るのか
入力バリデーションは、アプリケーションが受け取ったデータが「想定どおりの形をしているか」を確かめ、外れているものを拒否する処理です。年齢欄に負の数や文字列が来ていないか、メールアドレスらしき形式か、選択肢が決められた候補のいずれかか、といった検証がこれにあたります。
ここで押さえておきたいのは、入力検証には二つの層があるという点です。OWASPはこれを構文的検証(syntactic validation)と意味的検証(semantic validation)に分けて説明しています。構文的検証は「形式が正しいか」を見ます。日付が YYYY-MM-DD の形か、郵便番号が桁数どおりか、といった構造のチェックです。意味的検証は「業務的に筋が通るか」を見ます。開始日が終了日より前か、金額が現実的な範囲か、といった文脈に踏み込んだチェックです。両方をそろえて初めて、入力検証は実用的な防御になります。
入力検証が直接守るのは、主に業務ロジックの健全性です。想定外のデータが後続の処理に流れ込んで計算がおかしくなったり、極端に長い文字列でリソースを浪費させられたりするのを、入口で食い止めます。セキュリティの観点では、これは「攻撃の試行回数や種類を減らす一段目の壁」として価値があります。ただし、これだけで XSS や SQL インジェクションを防ぎきれるわけではない、という点が次の節の核心です。
なぜ入力検証だけでは攻撃を防げないのか
入力検証を「主たる防御」にできない理由は、煎じ詰めると「正当な入力として通さざるを得ない文字や形式に、攻撃も紛れ込むから」です。
分かりやすいのは自由記述のテキストです。ブログのコメント、商品レビュー、プロフィールの自己紹介といった欄は、原理的にあらゆる文字を受け付ける必要があります。記号 < や '、引用符を「危険だから」と一律に弾けば、5 < 10 と書きたい人や、O'Brien という名前の人が困ります。OWASPも、ユーザーは正当な理由で < や ' を送信し得るため、これらを入力段階で拒否するのは現実的でないと指摘しています。つまり、攻撃に使われる文字と正当な入力に使われる文字は重なっており、入口で機械的に線引きできないのです。
もう一つの理由は、危険性は「文字そのもの」ではなく「その文字がどこに出力されるか」で決まるという点です。同じ <script> という文字列でも、データベースの値として保存される分には何も起きません。それが無加工でHTMLとして画面に書き出された瞬間に、ブラウザがコードとして解釈して初めて危険になります。危険が生まれる場所は入口ではなく出口なのですから、出口での処理こそが本質的な対策になります。
注意
「入力時に危険な単語やパターンを弾けば安全」という拒否リスト(denylist / blocklist)方式は、主たる防御にはできません。大文字小文字の混在、エンコード、<img onerror=...> のようにscriptタグを使わない手法など、回避の余地が無数にあるためです。OWASPは拒否リストを「大きく欠陥のあるアプローチ(massively flawed)」と表現し、許可リスト方式を推奨しています。
入力検証とXSSの関係は、根本対策である出力エスケープと合わせて理解すると腑に落ちます。XSSの型ごとの仕組みは あわせて読みたい XSS(クロスサイトスクリプティング)の仕組みと対策。反射型・格納型・DOM型を原理から整理
入力検証を正しく組む: 許可リストと正規化
入力検証を主役にはできないとしても、補助的な防御として正しく組む価値は十分にあります。鍵は許可リスト(allowlist)方式です。
許可リスト方式とは、「通してよいものを定義し、それ以外はすべて拒否する」という考え方です。たとえば都道府県の選択であれば47都道府県のいずれか、ステータスであれば決められたコードのいずれか、というように、取り得る値が有限なら列挙して照合します。これに対し、拒否リスト方式は「危険なものを列挙して弾き、残りを通す」考え方ですが、攻撃手法は次々に新しくなるため、列挙が追いつかず破られます。守る側は許可リスト、つまり「安全と分かっているものだけを通す」発想で組むのが原則です。
自由記述のように取り得る値を列挙できない入力では、OWASPは次のような組み合わせを挙げています。まず正規化(normalization)で文字エンコードを正準形にそろえ、不正なバイト列を取り除きます。次にUnicodeのカテゴリ単位での許可、たとえば「文字(letters)」「十進数字(decimal digits)」といったカテゴリで許可することで、日本語・キリル文字・アラビア文字などを国際的に許容しつつ制御文字を排します。さらにアポストロフィのような特定文字を個別に許可します。こうすることで、O'Brien のような正当な入力を保ちながら、想定外のデータを減らせます。
メールアドレスの検証は、入力検証の限界と現実解がよく表れる例です。RFC 5321が許す形式は驚くほど複雑で、厳密な正規表現で完全に判定しようとすると破綻します。OWASPは、構文的には @ の存在・安全な文字・妥当な長さ・ドメイン形式の確認といった緩めのチェックにとどめ、本当の確認は確認メール(時間制限つき・使い捨てのトークン)を送って到達性と所有を検証する意味的検証で行うことを勧めています。「形式の完璧な検証」を追いかけるより、「実在性の確認」に主眼を置くのが実務的です。
メモ
入力検証は、サーバー側で必ず行います。ブラウザ側(クライアント側)の検証はユーザー体験の向上には役立ちますが、攻撃者はブラウザを通さずに直接リクエストを送れるため、セキュリティ上の防御にはなりません。クライアント側の検証は「親切」、サーバー側の検証は「防御」と切り分けて考えてください。
出力エスケープとは何か、なぜ文脈で変わるのか
出力エスケープ(出力エンコード)は、データを別の文脈へ出力する直前に、その文脈でコードとして解釈され得る文字を無害な表現へ変換する処理です。インジェクション系脆弱性の根本対策は、まさにこの出口での処理にあります。ただし出口での対策には二つの形があり、XSSのHTML出力のように文脈別エスケープを使う場合と、SQLインジェクションのように構文と値を分離する安全なAPI(プレースホルダ)を使う場合があります。OWASPのSQL Injection Prevention Cheat Sheetは、SQLでは全入力を手作業でエスケープする方式を「壊れやすく(fragile)」推奨できないとし、プリペアドステートメント等を主たる防御に挙げています。次節以降で両者を順に見ていきます。
なぜ出口なのか。インジェクション系脆弱性の本質は、「データ」を「コード(命令やマークアップ)」の一部としてそのまま混ぜ込み、両者の境界を壊してしまうことにあります。SQLインジェクションなら、ユーザー入力がSQL文の一部として解釈される。XSSなら、ユーザー入力がHTMLやJavaScriptの一部として解釈される。どちらも「データをコードとして出力してしまう」点が共通の原因です。だから対策も共通で、「データを出力するときに、それがコードとして解釈されない形に変換する」ことに尽きます。
ここで決定的に重要なのが、正しいエスケープ方式は出力先の文脈ごとに変わるという事実です。OWASPのXSS Prevention Cheat Sheetは、出力先のコンテキストごとに異なるエンコードを行うことを基本ルールとしています。同じ値でも、HTML本文に出すのか、HTML属性値に出すのか、JavaScriptの文字列に埋めるのか、URLのパラメータにするのかで、危険な文字も正しい変換方式も異なります。
| 出力先コンテキスト | 主な対処 |
|---|---|
| HTML本文 | & < > などをHTMLエンティティに変換する(& < > 等) |
| HTML属性値 | 属性を必ず引用符で囲み、英数字以外を &#xHH; 形式でエンコードする |
| JavaScript内の文字列 | \uXXXX 形式でエンコードし、必ず引用符で囲んだ文字列値の中にだけ埋める |
| CSSの値 | 値はプロパティ値の位置にだけ置き、それ以外のCSS文脈には埋めない |
| URL(パラメータ等) | パーセントエンコード(%HH)を行い、属性内ならHTML属性エンコードも併用する |
たとえばHTML本文に出すなら < を < に変換すれば <script> はタグとして解釈されません。しかし同じ値をJavaScriptの文字列リテラルの中に埋め込む場面では、HTMLエンティティ化は意味をなさず、Unicodeエスケープが必要です。OWASPは、JavaScriptで変数を安全に置ける唯一の場所は「引用符で囲まれたデータ値の中」だと述べています。コンテキストを取り違えたエスケープは、効いているように見えて穴が残る、という事故につながります。
SQLインジェクションでは「エスケープ」より「分離」
出力エスケープの考え方は、XSSだけでなくSQLインジェクションにも通じますが、SQLの場合はもう一歩進んだ最善策があります。それがプレースホルダ(プリペアドステートメント)によるパラメータ化です。
文字列としてSQL文を組み立てる際に値を手作業でエスケープする方法もありますが、エスケープ漏れや数値・識別子の扱いの違いで事故が起きやすい領域です。プレースホルダを使うと、SQLの構文(コード)と値(データ)を最初から別々にデータベースへ渡すため、値がSQL文の一部として解釈される余地そのものがなくなります。これは「出力時にエスケープして混ぜる」のではなく「そもそもコードとデータを混ぜない」設計であり、より根本的です。
-- 危険: 文字列連結で値をSQLに混ぜている
SELECT * FROM users WHERE name = '入力値';
-- 安全: プレースホルダで構文と値を分離する
SELECT * FROM users WHERE name = ?;
つまり、インジェクション対策には「出力時に文脈別エスケープを行う(XSSのHTML出力など)」と「そもそも構文とデータを分離する(SQLのプレースホルダなど)」という二つの実現形があり、可能なら後者の分離を選ぶ、という整理になります。どちらも「データをコードとして解釈させない」という同じ原理の現れです。SQLインジェクションの仕組みとプレースホルダの詳細は あわせて読みたい SQLインジェクションとは何か。仕組み・攻撃手法・影響・対策を原理から徹底解説
モダンフレームワークの自動エスケープと、その抜け道
ReactやVue、サーバーサイドのテンプレートエンジンの多くは、変数を画面に出すとき既定でHTMLエスケープを行います。これは大きな前進で、通常の使い方をしている限り、開発者が意識しなくても安全側に倒れます。
ただし、各フレームワークには「自動エスケープを意図的に外す」抜け道があり、そこが事故の温床になります。Reactの dangerouslySetInnerHTML、Vueの v-html、Angularの bypassSecurityTrust* などは、いずれも自動エスケープを外す操作です。名前のとおり危険であり、ユーザー由来の値をそのまま渡してはいけません。
// React: 通常の {value} は自動エスケープされる(安全)
<p>{value}</p>
// 危険: dangerouslySetInnerHTML は自動エスケープを外す
<p dangerouslySetInnerHTML={{ __html: value }} />
また、自動エスケープが効くのは基本的にHTML本文の文脈であって、属性やJavaScript、URLの文脈までフレームワークが完璧に面倒を見てくれるとは限りません。たとえば href に動的な値を入れる場合、javascript: スキームを許してしまえばクリックでスクリプトが走ります。出力先の文脈ごとに方式が変わるという原則は、フレームワークを使っていても消えないのです。「フレームワークが守ってくれているはず」という思い込みは、自動エスケープの範囲外で破られます。
テンプレートの自動エスケープを信頼しきっていて、リンクのhref属性にユーザー入力をそのまま入れていた箇所が後から見つかった、という指摘はよく聞きます。本文の表示は守られていても、属性やURLの文脈は別物だと、コードレビューの観点に加えておくと取りこぼしが減ります。
ユーザーにHTMLを書かせる場合のサニタイズ
リッチテキストエディタなどで、ユーザーに太字やリンクといったある程度のHTMLを許可したい場面では、単純なエスケープでは要件を満たせません。<b> をエスケープすれば太字にならないからです。この場合は、許可するタグ・属性だけを残して危険な要素を取り除く「サニタイズ」を行います。
ここでも原則は許可リスト方式です。安全と分かっているタグ・属性だけを通し、それ以外は除去します。OWASPはクライアント側のHTMLサニタイズにDOMPurifyを推奨しています。サニタイズは攻撃回避手法との終わりなき追いかけっこになりやすい領域なので、自前で正規表現を書かず、広く検証された実績あるライブラリに任せるのが鉄則です。
なお、サニタイズはあくまで「HTMLを許可したい」という特殊な要件のための手段です。HTMLを許可する必要がない大多数の出力先では、サニタイズより単純で確実な「文脈別エスケープ」を選んでください。要件に対して過剰に複雑な仕組みを入れると、かえって穴が生まれます。
根本対策と保険的対策を分けて重ねる
ここまでを、IPAの整理の枠組みで俯瞰すると見通しがよくなります。IPAの「安全なウェブサイトの作り方」は、脆弱性対策を「根本的解決」と「保険的対策」に分けて示しています。根本的解決は脆弱性の原因そのものをなくす対策、保険的対策は万一の際に攻撃の影響を低減する対策です。
この枠組みに当てはめると、出力エスケープやプレースホルダによる分離は根本的解決にあたります。一方、入力検証は(自由記述などでは主たる防御になりきれないため)多くの場面で保険的対策・一段目の壁という位置づけになります。さらにCSP(Content Security Policy)やWAFも保険的対策です。重要なのは、根本的解決を必ず据えた上で、保険的対策を重ねるという順序です。保険だけを厚くして根本を欠くと、土台のない防御になってしまいます。
よくある誤解と、やってはいけない判断
入力検証と出力エスケープをめぐっては、対策の優先順位を取り違える誤解が起きがちです。
- 入力をしっかり検証すればエスケープは不要: なりません。自由記述のように危険な文字を通さざるを得ない入力があり、危険が生じるのは出力時です。根本対策は出口のエスケープ(または分離)です。
- どこでも同じエスケープ関数を使えばよい: HTML用のエスケープをJavaScript文脈やURL文脈に流用すると穴が残ります。出力先の文脈ごとに正しい方式を使う必要があります。
- 拒否リストで危険な単語を弾けば十分: 回避手法が無数にあり、主たる防御にはできません。許可リスト方式が原則です。
- クライアント側で検証しているから安全: 攻撃者はブラウザを経由せず直接リクエストを送れます。検証は必ずサーバー側で行います。
- フレームワークを使っているから自動で安全: 本文の自動エスケープは効いても、
dangerouslySetInnerHTMLや属性・URLの文脈は守備範囲外です。
判断の軸は「危険な入力を頑張って取り除いているか」ではなく、「データを出力するその瞬間に、その文脈で安全な形に変換(または分離)されているか」です。この一点を基準にすると、対策の優先順位を見誤りにくくなります。
よくある質問
入力バリデーションと出力エスケープ、どちらが重要ですか?
入力時に危険な文字を弾けばXSSやSQLiは防げますか?
なぜ出力エスケープは場所によって方式が変わるのですか?
許可リストと拒否リストはどちらで組むべきですか?
フレームワークの自動エスケープがあれば安心ですか?
入力検証はクライアント側だけで十分ですか?
まとめ
入力検証・出力エスケープのチェックリスト
- XSS/SQLiの根本対策を出口(文脈別エスケープ・プレースホルダによる分離)に置いているか
- 入力検証を主たる防御ではなく、防御の一段目・保険的対策として位置づけているか
- 入力検証は許可リスト方式で組み、拒否リストに依存していないか
- 入力検証を必ずサーバー側で行っているか(クライアント側は補助)
- 出力エスケープを出力先の文脈(HTML本文/属性/JavaScript/URL)ごとに正しく行っているか
- dangerouslySetInnerHTMLやv-html、属性・URLの文脈など自動エスケープの抜け道を確認したか
- ユーザーにHTMLを許可する箇所は、DOMPurify等のサニタイザを許可リスト方式で使っているか
- 根本的解決を据えた上で、CSPやWAF等の保険的対策を重ねているか
入力バリデーションと出力エスケープは、よく一緒に語られますが、守る対象も働く場所も違います。入口の検証は「想定外を早めに拒否する一段目」、出口のエスケープは「データをコードと誤解させない根本対策」です。この役割分担を取り違えず、出口に根本対策を据えた上で入口の検証を重ねる、という順序を守ることが、脆弱性を作り込まない設計の出発点になります。XSSやSQLインジェクションが結局「コードとデータを混ぜない」という同じ原理に行き着くことは、 あわせて読みたい XSS(クロスサイトスクリプティング)の仕組みと対策。反射型・格納型・DOM型を原理から整理 あわせて読みたい SQLインジェクションとは何か。仕組み・攻撃手法・影響・対策を原理から徹底解説
出典・参考
関連する記事
XSS(クロスサイトスクリプティング)の仕組みと対策。反射型・格納型・DOM型を原理から整理
代表的なWeb脆弱性であるXSSを、なぜ起きるのかという原理から、反射型・格納型・DOM型の違い、想定される影響、根本対策である出力エスケープ(コンテキスト別エンコード)とCSPによる多層防御まで、実務目線で体系的に解説します。
SQLインジェクションとは何か。仕組み・攻撃手法・影響・対策を原理から徹底解説
代表的なWeb脆弱性であるSQLインジェクションを、なぜ起きるのかという原理から、攻撃手法の分類、想定される影響、根本対策であるプレースホルダの使い方、多層防御、検出方法までを実務目線で網羅的に解説します。
Webセキュリティを学ぶ人へ。体系的に学べるおすすめ書籍と読む順番
セキュリティを基礎から体系的に学びたい人に向けて、定番として評価の高い書籍を用途別に厳選して紹介します。Web脆弱性・暗号・ネットワーク・手を動かす実践まで、何をどの順で読むべきかも解説します。


