入力値の検証
Input Validation Cheat Sheet
- 原典
- Input Validation Cheat Sheet(OWASP Cheat Sheet Series)
- 原文
- GitHub 上の Markdown
- 底本
bac04fb5(2026-07-29 時点)- ライセンス
- CC BY-SA 4.0(原典と同一。訳文も同ライセンスで再配布できます)
はじめに
この記事は、アプリケーションに入力検証のセキュリティ機能を持たせるための、明確で単純な、実行可能な指針を示すことに主眼を置いている。
入力検証の目的
入力検証は、情報システムのワークフローに適切な形式のデータだけが入ることを保証するために行う。 不正な形式のデータがデータベースに残り、下流の各構成要素の誤動作を引き起こすことを防ぐ。 入力検証はデータの流れのできるだけ早い段階、望ましくは外部から受け取った直後に行うべきである。
信頼できない可能性のあるすべての情報源からのデータを入力検証の対象にする。 インターネットに面した Web クライアントだけでなく、エクストラネット経由で供給元、提携先、ベンダー、規制機関から来るバックエンドのフィードも含める。 これらはそれぞれが独自に侵害され、不正な形式のデータを送り始める可能性がある。
入力検証を、XSS や SQL インジェクション、その他それぞれのチートシートで扱う攻撃を防ぐ主要な手段として使うべきではない。 ただし適切に実装されれば、それらの影響を減らすことに大きく寄与する。
入力検証の戦略
入力検証は構文と意味の両方のレベルで適用するべきである。
- 構文の検証は、構造を持つフィールド(社会保障番号、日付、通貨記号など)の正しい構文を強制する。
- 意味の検証は、特定の業務上の文脈におけるそれらの値の正しさを強制する(開始日が終了日より前である、価格が想定される範囲に収まっているなど)。
利用者(攻撃者)のリクエストの処理において、できるだけ早い段階で攻撃を防ぐことが常に推奨される。 入力検証は、アプリケーションが処理する前に不正な入力を検出するために使える。
入力検証の実装
入力検証は、構文と意味の正しさを実効的に強制できるプログラミング手法であれば、どの手法でも実装できる。 たとえば次のようなものである。
- Web アプリケーションフレームワークが標準で備えるデータ型の検証機構を使う(Django Validators、Apache Commons Validators など)。
- これらの形式の入力に対しては、JSON Schema や XML Schema(XSD)による検証を行う。
- 厳密な例外処理を伴う型変換(Java の
Integer.parseInt()、Python のint()など)。 - 数値パラメータと日付に対する最小値と最大値の範囲確認、文字列に対する最小長と最大長の確認。
- 少数の値からなる文字列パラメータ(曜日など)に対する、許可される値の配列。
- その他の構造を持つデータに対する正規表現。入力文字列全体を対象とし(
(^...$))、「任意の文字」を表すワイルドカード(.や\Sなど)は使わない。 - 既知の危険なパターンの拒否リストは、防御の追加の層として使える。ただしこれは許可リストを補うものであって置き換えるものではなく、主要な検証手段として依存せずに、よく観測される攻撃やパターンの一部を捕まえる助けとして用いる。
許可リストと拒否リスト
アポストロフィ ' の文字、1=1 という文字列、<script> タグのような危険になりうる文字やパターンを検出しようとして拒否リスト方式の検証を使うのは、よくある誤りである。
攻撃者がそうしたフィルタを回避するのは容易であり、この方法には大きな欠陥がある。
さらに、そうしたフィルタは O'Brian のように ' が完全に正当である入力を、しばしば拒んでしまう。
XSS フィルタの回避についてさらに詳しくはこの Wiki のページを参照する。
拒否リストは、よくある悪意あるパターンの一部を捕まえる防御の追加の層としては有用であるが、主要な手段として依存すべきではない。 有害になりうる入力の防止には、許可リストのほうが堅牢で安全な方法である。
許可リスト方式の検証は、利用者が与えるすべての入力フィールドに適している。 許可リスト方式の検証は、何が認められるのかを正確に定義することであり、その定義により、それ以外のすべては認められない。
日付、社会保障番号、郵便番号、メールアドレスのように構造の明確なデータであれば、開発者はそうした入力を検証するための非常に強固な検証パターンを、通常は正規表現に基づいて定義できるはずである。
入力フィールドがドロップダウンリストやラジオボタンのように選択肢の固定された集合から来る場合、その入力は最初に利用者へ提示された値のいずれか一つに正確に一致しなければならない。 サーバー側でこの離散的な選択肢の一覧に対する検証が失敗した場合、それは重大なセキュリティ事象であり、高い深刻度の事象として記録するべきである。 クライアント側のコードを攻撃者が改変していることを示しているからである。
自由記述の Unicode テキストの検証
自由記述のテキスト、とくに Unicode の文字を含むものは、許可しなければならない文字の空間が比較的大きいため、検証が難しいと受け取られている。
そして、適切な文脈認識の出力エンコーディングの重要性を際立たせ、入力検証がクロスサイトスクリプティングに対する主要な防御ではないことを明白に示すのも、この自由記述テキストの入力である。
利用者がコメント欄にアポストロフィ ' や小なり記号 < を打ちたいのであれば、まったく正当な理由があるかもしれない。
それをデータの生存期間全体にわたって適切に扱うのがアプリケーションの仕事である。
自由記述テキストの入力に対する入力検証の主要な手段は次のものであるべきである。
- 正規化:テキスト全体で正規のエンコーディングが使われ、不正な文字が含まれないようにする。
- 文字カテゴリの許可リスト:Unicode では「十進数字」や「文字」といったカテゴリを列挙できる。これはラテン文字だけでなく、世界で使われる他の文字体系(アラビア文字、キリル文字、CJK 表意文字など)も対象にする。
- 個々の文字の許可リスト:氏名において文字と表意文字を許可し、さらにアイルランド系の姓のためにアポストロフィ
'を許可したいが、句読点のカテゴリ全体は許可したくない場合に用いる。
参考資料
- Input validation of free-form Unicode text in Python
- UAX 31: Unicode Identifier and Pattern Syntax
- UAX 15: Unicode Normalization Forms
- UAX 24: Unicode Script Property
正規表現
正規表現の作成は複雑になりうるものであり、このチートシートの範囲を大きく超える。
正規表現の書き方についてはインターネット上に多くの資料がある。 regular-expressions.info や OWASP Validation Regex Repository などである。
正規表現を設計するときは、正規表現によるサービス妨害(ReDoS)攻撃に注意する。 この攻撃は、設計の悪い正規表現を使うプログラムの動作を非常に遅くし、長時間にわたって CPU 資源を消費させる。
要するに、入力検証は次を満たすべきである。
- 少なくとも、すべての入力データに適用する。
- 受け付ける文字の集合を定義する。
- データの最小長と最大長を定義する(
{1,25}など)。
許可リスト方式の正規表現の例
米国の郵便番号(5 桁に任意の -4 が続く)の検証
^\d{5}(-\d{4})?$
ドロップダウンメニューからの米国の州の選択の検証
^(AA|AE|AP|AL|AK|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|
HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MT|NE|
NV|NH|NJ|NM|NY|NC|ND|MP|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|
TX|UT|VT|VI|VA|WA|WV|WI|WY)$
Java における正規表現の使用例
正規表現を用いて zip パラメータを検証する例を示す。
private static final Pattern zipPattern = Pattern.compile("^\d{5}(-\d{4})?$");
public void doPost( HttpServletRequest request, HttpServletResponse response) {
try {
String zipCode = request.getParameter( "zip" );
if ( !zipPattern.matcher( zipCode ).matches() ) {
throw new YourValidationException( "Improper zipcode format." );
}
// do what you want here, after its been validated ..
} catch(YourValidationException e ) {
response.sendError( response.SC_BAD_REQUEST, e.getMessage() );
}
}
許可リスト方式の検証機構は、各種のオープンソースのパッケージにあらかじめ定義されているものもあり、それを活用できる。 たとえば次のものがある。
クライアント側の検証とサーバー側の検証
入力検証は、アプリケーションの機能がデータを処理する前に、サーバー側で実装しなければならない。 クライアント側で JavaScript によって行う入力検証は、JavaScript を無効にしたり Web プロキシを使ったりする攻撃者に回避されうるからである。 利用者体験のためにクライアント側の JavaScript による検証を、安全性のためにサーバー側の検証を、それぞれの強みを活かして両方実装するのが推奨される方法である。
リッチな利用者コンテンツの検証
利用者が投稿したリッチコンテンツの検証は非常に難しい。 詳しくは XSS のチートシートのHTML のサニタイズの節を参照する。
XSS の防止と Content Security Policy
利用者が制御できるデータは、悪意あるデータ(XSS など)の実行を防ぐため、HTML ページで返すときにすべてエンコードしなければならない。
たとえば <script> は <script> として返される。
エンコードの種類は、利用者が制御するデータが挿入されるページ上の文脈に固有である。 たとえば HTML の本体に置かれるデータには HTML エンティティエンコーディングが適切である。 しかしスクリプトの中に置かれる利用者のデータには、JavaScript に固有の出力エンコーディングが必要になる。
XSS 防止の詳細はクロスサイトスクリプティングの防止にある。
ファイルアップロードの検証
多くの Web サイトは、プロフィール画像などのファイルのアップロードを利用者に許している。 この節は、その機能を安全に提供する助けとなる。
ファイルアップロードも参照する。
アップロードの確認
- 入力検証を用いて、アップロードされたファイル名が想定される拡張子を使っていることを確認する。
- アップロードされたファイルが、定めた最大サイズを超えていないことを確認する。
- サイトが ZIP ファイルのアップロードに対応している場合は、展開する前に検証を行う。確認する項目には、展開先のパス、圧縮率、展開後の推定サイズが含まれる。
アップロードしたファイルの保存
- OS 上にファイルを保存する際は新しいファイル名を使う。このファイル名にも一時ファイル名にも、利用者が制御できるテキストを使わない。
- ファイルが Web にアップロードされたときは、保存時にファイル名を変えることが推奨される。たとえばアップロードされたファイル名が test.JPG であれば、JAI1287uaisdjhf.JPG のようなランダムなファイル名に変える。目的は、ファイルへの直接アクセスのリスクと、
test.jpg;.aspや/../../../../../test.jpgのようにフィルタを回避する曖昧なファイル名のリスクを防ぐことである。 - アップロードされたファイルは悪意あるコンテンツについて解析するべきである(マルウェア対策、静的解析など)。
- クライアントがファイルのパスを指定できてはならない。パスはサーバーが決める。
アップロードしたコンテンツの一般公開
- アップロードされた画像が正しい content-type(
image/jpeg、image/pngなど)で配信されることを確認する。
特定のファイル種別に注意する
アップロード機能は許可リスト方式を用い、特定のファイル種別と拡張子のみを許可するべきである。 ただし、許可した場合にセキュリティ上の脆弱性につながりうる次のファイル種別に注意することが重要である。
- crossdomain.xml および clientaccesspolicy.xml:Flash、Java、Silverlight におけるドメインをまたぐデータ読み込みを許可する。認証のあるサイトで許可されると、ドメインをまたぐデータの窃取や CSRF 攻撃を可能にしうる。対象のプラグインのバージョンによってかなり複雑になりうるので、"crossdomain.xml" と "clientaccesspolicy.xml" という名前のファイルは単に禁止するのが最善である。
- .htaccess と .htpasswd:ディレクトリ単位でサーバーの設定を与えるものであり、許可すべきではない。HTACCESS の解説を参照する。
aspx, asp, css, swf, xhtml, rhtml, shtml, jsp, js, pl, php, cgiのような Web で実行可能なスクリプトファイルは許可しないことが推奨される。
画像アップロードの確認
- 画像の書き換えライブラリを用いて、画像が正当であることを確認し、余分なコンテンツを取り除く。
- 保存する画像の拡張子は、画像処理によって検出したコンテンツ種別に基づく正当な画像の拡張子にする(アップロード時のヘッダを単に信用しない)。
- 検出した画像のコンテンツ種別が、定めた画像種別の一覧(jpg、PNG など)に含まれることを確認する。
メールアドレスの検証
構文の検証
メールアドレスの形式は RFC 5321 で定義されており、ほとんどの人が思っているよりはるかに複雑である。 例として、次はいずれも正当なメールアドレスとみなされる。
"><script>alert(1);</script>"@example.orguser+subaddress@example.orguser@[IPv6:2001:db8::1]" "@example.org
正規表現でメールアドレスの正当性を適切に解析するのは非常に複雑であるが、正規表現に関する公開文書がいくつか存在する。
最大の注意点は、RFC がメールアドレスに対して非常に柔軟な形式を定義している一方で、現実の実装(メールサーバーなど)のほとんどはずっと制限された形式を用いており、技術的には正当なアドレスを拒否することである。 技術的には正しくても、アプリケーションが実際にメールを送れないのであれば、そうしたアドレスはほとんど役に立たない。
したがってメールアドレスを検証する最善の方法は、基本的な初期検証を行い、そのアドレスをメールサーバーへ渡し、拒否されたら例外を捕まえることである。 これにより、受け付けたすべてのアドレスに対して自分のメールサーバーがメールを送れるという確信をアプリケーションが持てる。 初期検証は次のように単純なものでよい。
- メールアドレスが
@記号で区切られた二つの部分からなる。 - メールアドレスが危険な文字(バッククォート、単一引用符、二重引用符、ヌルバイトなど)を含まない。
- どの文字が危険であるかは、そのアドレスがどう使われるか(ページに出力される、データベースに挿入されるなど)によって変わる。
- ドメイン部分が英字、数字、ハイフン(
-)、ピリオド(.)のみを含む。 - メールアドレスの長さが妥当である。
- ローカル部分(
@の前)は 63 文字以内とする。 - 全体の長さは 254 文字以内とする。
- ローカル部分(
意味の検証
意味の検証は、そのメールアドレスが正しく、かつ実在するものかを判断することである。 最も一般的な方法は、利用者にメールを送り、そのメールに含まれるリンクをクリックさせるか、送られたコードを入力させることである。 これによって次の点について基本的な確度が得られる。
- そのメールアドレスが正しい。
- アプリケーションがそこへメールを送れる。
- 利用者がそのメールボックスにアクセスできる。
所有を証明するために利用者へ送るリンクには、次の条件を満たすトークンを含めるべきである。
- 長さが 32 文字以上である。
- 安全な乱数源を用いて生成されている。
- 一度しか使えない。
- 有効期限がある(8 時間で失効するなど)。
メールアドレスの所有を検証したあと、利用者には通常の機構を通じてアプリケーションでの認証を求めるべきである。
使い捨てのメールアドレス
利用者がアプリケーションへの登録時に実際のメールアドレスを渡したくない場合、代わりに使い捨てのメールアドレスを与えることがある。 これは認証を必要としない公開のアドレスであり、通常は主要なメールアドレスに届く迷惑メールを減らすために使われる。
使い捨てのメールアドレスを遮断することはほぼ不可能である。 このサービスを提供するサイトは多数あり、新しいドメインが毎日作られている。 既知の使い捨てドメインの公開の一覧や商用の一覧はいくつかあるが、それらは常に不完全である。
こうした一覧を使って使い捨てのメールアドレスの利用を遮断する場合、なぜ遮断されたのかを説明するメッセージを利用者に示すべきである(ただし利用者は、正当なアドレスを与えるのではなく、別の使い捨てサービスを探すだけである可能性が高い)。
使い捨てのメールアドレスを遮断することがどうしても必要であれば、明示的に許可したメール事業者からの登録のみを認めるべきである。 ただしそこに Google や Yahoo のような公開の事業者を含めると、利用者はそこで自分用の使い捨てアドレスを登録できてしまう。
サブアドレス
サブアドレスは、メールアドレスのローカル部分(@ の前)にタグを指定することを利用者に許すもので、そのタグはメールサーバーによって無視される。
たとえば example.org ドメインがサブアドレスに対応していれば、次のメールアドレスは同等である。
user@example.orguser+site1@example.orguser+site2@example.org
多くのメール事業者(Microsoft Exchange など)はサブアドレスに対応していない。 対応している事業者で最も有名なのは Gmail であるが、他にも対応しているものは多い。
利用者によっては、登録する Web サイトごとに異なるタグを使う。 そうすればサブアドレスのいずれかに迷惑メールが届き始めたときに、どのサイトが自分のメールアドレスを漏洩または売却したのかを特定できる。
一つのメールアドレスで複数のアカウントを登録できてしまうため、サイトによっては + と @ のあいだをすべて除去してサブアドレスを遮断したいと考える。
これは一般には推奨されない。
サイトの運営者がサブアドレスを知らないか、メールアドレスを漏洩または売却したときに利用者から特定されるのを防ぎたいと考えていることを示唆するからである。
加えて、使い捨てのメールアドレスを使うか、信頼できる事業者で複数のメールアカウントを登録するだけで、容易に回避できる。