プログラミング手法としての入力値検証により、適切にフォーマットされたデータだけがソフトウェアシステムコンポーネントに入力されるようになります。
アプリケーションは、データをどのような方法で利用する場合においても使用する前にシンタックスおよびセマンティック(この順で)な検証を行うべきです。 シンタックス検証とは、データが想定される形式であることを確認することです。 例えば、何らかの操作を実行するために4桁の「アカウントID」をユーザが選択できるとします。 アプリケーションは、ユーザがSQLインジェクションペイロードを入力していることも想定し、ユーザが入力したデータが正確に4桁の長さであることを確認し、数字だけで構成されていることを確認する必要があります。(加えて適切なクエリのパラメータ化を行います。) セマンティック検証は、与えられたアプリケーションの機能性およびコンテキストに対して許容可能な範囲内にある入力のみとすることを確認することです。 例えば、日付範囲を選択する場合には開始日を終了日以前にする必要があります。
インプットシンタックス検証を行う手法として一般的にはブラックリスティング手法とホワイトリスティング手法があります。
- ブラックリスティングまたはブラックリストによる検証は、データに「既知の不良」なコンテンツが含まれていないことを確認します。例えば、Webアプリケーションは、XSSを防止するために、正確なテキスト<SCRIPT>を含む入力をブロックすることがあります。 しかし、この防御は、小文字のスクリプトタグまたは大文字と小文字の混在したスクリプトタグを使用して回避することができます。
- ホワイトリスティングまたはホワイトリストによる検証は、データが「既知の良い」ルールと一致するかどうかを確認します。例えば、米国州のホワイトリストによる検証規則は、2文字という米国州として有効な唯一のコードになります。 セキュアなソフトウェアを構築する場合は、ホワイトリスティングが推奨アプローチです。 ブラックリスティングは、エラーが発生しやすくなり、さまざまな回避手法を使用してバイパスすることができ、それ自体に依存すると危険になる可能性があります。 ブラックリスティングはしばしば回避されますが、明白な攻撃を検出するのに役立つことがよくあります。 したがって、ホワイトリスティングは、正しいシンタックスおよびセマンティック検証によって攻撃サーフィスを制限するのに役立ちますが、ブラックリスティングは明らかな攻撃を検出し、それを潜在的に阻止するのに役立ちます。
入力値検証は、サーバサイドで常に実行する必要があります。 クライアントサイドの検証は、機能的な目的とセキュリティの目的の両方に役立ちますが、しばしば簡単にバイパスされます。 したがって、サーバーサイドの検証が重要になります。 例えば、JavaScriptの検証では、特定のフィールドは数値で構成されている必要があることをユーザーに警告することができますが、サーバサイドのアプリケーションでは、送信されたデータがその機能の適切な数値の範囲内の数値のみであることを検証する必要があります。
正規表現は、データが特定のパターンと一致するかどうかを確認する方法です。 基本的な例から始めましょう。 次の正規表現を使用して、ユーザ名を検証するホワイトリストルールを定義します。
^[a-z0-9_]{3,16}$
この正規表現では、小文字、数字、アンダースコアのみを使用できます。 ユーザ名は、3文字と16文字に制限されています。
正規表現を作成するときには注意が必要です。 不適切に設計された表現は、サービス拒否状態(ReDoSとも呼ばれます)を起こす可能性があります。さまざまなツールにより正規表現がReDoSに対して脆弱ではないことを検証するテストを実施できます。
正規表現は、検証を行うための1つの方法でしかありません。 正規表現は、一部の開発者にとっては維持や理解が難しい場合があります。 他の検証の選択肢には、プログラムによって検証メソッドを記述することが含まれ、その方法の方が一部の開発者にとっては維持が容易な可能性があります。
特定の形式の複雑な入力が「有効」であるものの、依然として危険であるかもしれないため、入力値検証が必ずしもデータを「安全」にするとは限りません。 例えば、有効な電子メールアドレスにSQLインジェクション攻撃が含まれているか、有効なURLにクロスサイトスクリプティング攻撃が含まれている可能性があります。 クエリのパラメータ化やエスケープなどの入力値検証以外の追加の防御を常に適用する必要があります。
入力の種類によっては、検証がアプリケーションを最小限に保護するだけの複雑なものもあります。 例えば、攻撃者が操作できる信頼できないデータやデータをデシリアライズ化することは危険です。 安全なアーキテクチャパターンは、信頼できないソースからのシリアライズ化したオブジェクトを受け入れないか、単純なデータ型に対してのみ限定されたデリシリアライズ化することです。 シリアライズ化されたデータ形式を処理することは避け、可能であればJSONなどのフォーマットを利用すべきです。 それが不可能な場合は、シリアライズ化されたデータを処理する際に、一連の検証防御を行うことを検討してください。
- 悪意のあるオブジェクトの作成やデータの改ざんを防ぐために、シリアライズ化されたオブジェクトの完全性の確認や暗号化を実装します。
- オブジェクトの作成前のデシリアライズ化の際に厳密な型制約を強制する。 通常コードには定義可能なクラスのセットがあります。 この手法へのバイパスが実証されています。
- デシリアライズ化するコードを分離して、一時的なコンテナなどの非常に低い特権環境で実行します。
- 後に来るタイプが予想されるタイプでない場合や、デシリアライズ化が例外を投げる場合など、セキュリティのデシリアライズ化の例外および失敗を記録します。
- デシリアライズ化するコンテナまたはサーバーからの後に来て出力されるネットワーク接続を制限または監視します。
- デシリアライズ化を監視し、ユーザーが常時デシリアライズ化を行う場合には警告します。
一部のフレームワークでは、アプリケーションによって使用されるサーバサイドのオブジェクトへのHTTP要求パラメータのオートバインドがサポートされています。 この自動バインド機能により、攻撃者は変更を意図しないサーバサイドのオブジェクトを更新できます。 攻撃者は、この機能を使用してアクセス制御レベルを変更したり、アプリケーションの意図したビジネスロジックを迂回する可能性があります。 この攻撃には、マスアサインメント、オートバインド、オブジェクトインジェクションなど、いくつかの名前があります。 簡単な例として、ユーザオブジェクトにアプリケーションのユーザ特権レベルを指定するフィールド特権がある場合、悪意のあるユーザはユーザデータが変更されたページを検索し、送信されたHTTPパラメータにprivilege = adminを追加できます。 安全でない状態でオートバインドが有効になっている場合は、ユーザを表すサーバサイドのオブジェクトが変更されます。 これを防ぐ方法は2つあります。
- 入力を直接バインドすることを避け、代わりにデータ転送オブジェクト(DTO)を使用します。
- オートバインドを有効にするが、オートバインドできるフィールドを定義するために、ページまたは機能ごとのホワイトリストルールを設定します。 詳細は、 OWASP Mass Assignment Cheat Sheetをご確認ください。
ユーザからHTMLを受け入れる必要のあるアプリケーション(コンテンツをHTMLとして表現するWYSIWYGエディターまたはHTMLを入力に直接受け入れる機能を使用)を検討してください。 この状況では、検証やエスケープは役に立ちません。
- 正規表現はHTML5の複雑さを理解するのに十分な表現ではありません。
- HTMLのエンコーディングまたはエスケープは、HTMLが正しくレンダリングされないために役立ちません。 したがって、HTML形式のテキストを解析して整理できるライブラリが必要です。 HTMLサニタイズの詳細については、XSS Prevention Cheat Sheet on HTML Sanitizationをご確認ください。
すべての言語とほとんどのフレームワークは、データの検証に活用すべき検証ライブラリまたは機能を提供します。 検証ライブラリは、通常一般的なデータ型、長さ要件、整数範囲、「ヌル」チェックなどをカバーします。 多くの検証ライブラリとフレームワークでは、開発者がアプリケーション全体でその機能を活用できるように、独自の正規表現やカスタム検証のロジックを定義することができます。 検証機能の例として、PHPのfilter functions やJava用のHibernate Validator があります。 HTMLサニタイザの例として、Ruby on Rails sanitize method、OWASP Java HTML Sanitizer、DOMPurify などがあります。
- 入力値検証により、アプリケーションの攻撃サーフィスが減少し、アプリケーションに対する攻撃をより困難にすることができます。
- 入力値検証は、特定の形式のデータに対してセキュリティを提供する手法であり、一般的なセキュリティルールとして確実に適用できない手法です。
- 入力値検証をXSS、SQLインジェクションなどの攻撃を防止する主要な方法として使用しないでください。