====== デフォルトで初期化される外部入力をバリデーションする ====== PHPは多くの外部からの入力を自動的に初期化し、スーパーグローバル配列として利用可能にしている。外部からの入力であることは明らかな物もあるが、そうでない物もある。 * $_GET - 変数名を含め、全て外部からの入力。デコード済み * $_POST - 変数名を含め、全て外部からの入力。デコード済み * $_COOKIE - 変数名を含め、全て外部からの入力。デコード済み * $_REQUEST - 変数名を含め、全て外部からの入力。デコード済み * $_FILES - アップロードされたファイル以外に、ファイル名やファイルタイプも外部入力。デコード済み * $_ENV - 変数名を含め、全て外部からの入力。 * $_SERVER - 変数名はフィルタ処理される物が多いが外部入力、値は外部からの入力の物が多い。 スーパーグローバル変数以外の外部入力が変数として初期化される物は以下である * $argv, $argc - $argcはシステムが初期化したパラメータ数。$argvの内容は外部入力。 * $php_errormsg - 直前のエラーのエラーメッセージ。エラーメッセージ内に外部入力が含まれる可能性がある。 * $http_response_header - PHPストリームでURLを処理した場合に初期化される配列。全て外部入力。 * $HTTP_RAW_POST_DATA - POSTメソッドデータ。デコード処理が行われていない外部入力。(PHP 7.0で廃止。代わりにphp://inputストリームを利用) 外部入力のデータは全てバリデーションを行うべきあり、バリデーションを省略できるのはデータ元となるシステム、データを経由するネットワークなどの経路およびデータその物が信頼できる場合に限る。例えば、信頼できるデータベースサーバーとネットワークであっても、データベースに保存されているデータが信頼できない物(第三者が情報を保存/編集できる物)である場合はそのデータを信頼することはできない点に注意する。 Webアプリケーションで取り扱うデータは基本的にすべて文字列である点に留意してバリデーションを行う。バリデーションは全ての外部入力に対して行い、ロジックは以下のチェックを行わなければならない * データの大きさ(最小サイズ、最大サイズ) * 形式の確認 (整数のみ、半角英字のみ、などをホワイトリスト方式で確認) * 値の範囲(整数/浮動小数点の場合、最小値と最大値) * パラメータの数 また入力その物の真正性のバリデーションが必要となる場合も多い。例えば、データ操作をともなうURLへのリクエストが実際にユーザーが意図した操作であるかは、バリデーションを行わないとCSRF(クロスサイト・リクエスト・フォージェリ)攻撃が可能になる。 このルールに非適合となるコード例は多岐に渡る。ここで紹介する非適合コード例は一部に過ぎない。 ===== 非適合コード例(Integer handling)===== 前提条件:システムは正の整数IDを受け入れるシステムとする。 $_GET/$_POST/$_COOKIE配列を直接利用すれば外部入力であることは明白だが、別の変数に代入すると外部入力であるかどうか不明確になる。 **非適合コード** $id = $_GET['id']; キャストはバリデーションの代替とはならない。 **非適合コード** $id = (int)$_GET['id']; キャストでは$idの範囲は正の整数のみであるにも関わらず負の数が設定される、文字列などが0に初期化される、など不正な入力を受け入れる。 ===== 適合コード例 (filter_input) ===== **適合コード** $id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT, ['min_range'=>100, 'max_range'=>PHP_INT_MAX]); if (!is_numeric($id)) { throw new Exception('Invalid ID') ; } filter_input/filter_varの整数型バリデーションフィルター(FILTER_VALIDATE_INT)はPHPの整数型の範囲内で整数値であることをバリデーションする。PHPの整数型は利用するOSにより符号付き32ビット整数か符号付き64ビット整数のどちらかになる点に注意が必要である。 ===== 適合コード例 (is_numeric/is_int and range check)===== **適合コード** if (!is_numeric($_GET['id']) || $_GET['id'] < 100 || is_int($_GET['id']*1) { throw new Exception('Invalid ID') ; } このコードfilter_var()/filter_input()は整数型で表現可能な範囲外の値にはfalseを返す。"$GET['id']*1"は$GET['id']の値が整数型で表現できない場合に浮動小数点型に変換されることを用いてオーバーフローを検出している。PHPの整数型は利用するOSにより符号付き32ビット整数か符号付き64ビット整数のどちらかになる点に注意が必要である。 ===== 非適合コード例(Text handling)===== 前提条件: $_GET['name']は名前を保存したデータとする。文字エンコーディングはUTF-8とする。エラーイベントはデフォルトエラーハンドラで例外に変更され、デフォルト例外ハンドラが定義されているものとする。 テーブル定義: CREATE TABLE mytable ( "name" text ); **非適合コード** PostgreSQLは文字列型(text/char/varcahr型)は文字エンコーディングをバリデーションする。しかし、データベース文字エンコーディングがASCIIの場合、バリデーションは行われない。 ===== 適合コード例 (mb_check_encoding)===== **適合コード** 文字エンコーディングバリデーションが必要となる場面はデータベースに出力する時点のみでなく、XMLなどの処理にも必要である。このため、文字エンコーディングバリデーションは入力処理時に集中して行うと効率が良い。 ===== 例外 ===== エラー処理には例外利用を推奨するが、trigger_error()を用いても構わない。 外部入力となるデータソース、データが経由するネットワークなどの経路、データその物が信頼できる場合はバリデーションを省略可能な場合もある。このようなケースに該当するシステムは外部入力を一切含まないファイアーウォール内のデータベースなどであり、通常ではほぼない。データベースの場合、送信されてくるデータ型はテーブル定義と一致している事が期待できる。しかし、SQLiteのようにデータ型を強制しないデータベースシステムもある。 ===== リスク評価 ===== 外部入力のバリデーションを行わなかったり不備があった場合、任意コード実行、情報の漏洩、サービス不能など生む結果となる場合がある。 ^ Rule ^ Severity ^ Likelihood ^ Remediation Cost ^ Priority ^ Level ^ | | High | likely | medium | P15 | L3 | ===== 関連ガイドライン ===== * ISO27000 * [[https://www.securecoding.cert.org/confluence/display/seccode/Top+10+Secure+Coding+Practices|CERT Top 10 Secure Coding Practices]] * [[http://www.sans.org/top25-software-errors/|CWE/SANS TOP 25]] * [[http://https://www.owasp.org/index.php/OWASP_Secure_Coding_Practices_-_Quick_Reference_Guide|OWASP Secure Coding Practices - Quick Reference Guide]] * [[https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project|OWASP TOP 10]] * [[https://www.owasp.org/index.php/OWASP_Proactive_Controls|OWASP TOP 10 Proactive Security]] ===== 参考文献 ===== * http://blog.ohgaki.net/cert-top-10-secure-coding-standard * http://blog.ohgaki.net/sans-cwe-top-25-monster-mitigation * http://blog.ohgaki.net/owasp-secure-coding-practices-quick-reference-guide