Table of Contents

デフォルトで初期化される外部入力をバリデーションする

PHPは多くの外部からの入力を自動的に初期化し、スーパーグローバル配列として利用可能にしている。外部からの入力であることは明らかな物もあるが、そうでない物もある。

スーパーグローバル変数以外の外部入力が変数として初期化される物は以下である

外部入力のデータは全てバリデーションを行うべきあり、バリデーションを省略できるのはデータ元となるシステム、データを経由するネットワークなどの経路およびデータその物が信頼できる場合に限る。例えば、信頼できるデータベースサーバーとネットワークであっても、データベースに保存されているデータが信頼できない物(第三者が情報を保存/編集できる物)である場合はそのデータを信頼することはできない点に注意する。

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
);

非適合コード

<?php
$db = pg_connect('host=localhost');
 
$name = filter_var($_POST['name'], FILTER_DEFAULT, [FILTER_FALG_STRIP_LOW]);
if (strlen($name) !=== strlen($_POST['name']) {
  throw new Exception('Invalid name');
}
 
$sql = 'INSERT INTO mytable ("name") VALUES ($1);';
pg_query_params($sql, [$name]);
?>

PostgreSQLは文字列型(text/char/varcahr型)は文字エンコーディングをバリデーションする。しかし、データベース文字エンコーディングがASCIIの場合、バリデーションは行われない。

適合コード例 (mb_check_encoding)

適合コード

<?php
// Set default_charset
ini_set('default_charset', 'UTF-8');
// PHP 5.5 or less requires more encoding settings
ini_set('mbstring.internal_encoding', 'UTF-8');
mb_regex_encoding('UTF-8'); // The default was EUC-JP for PHP 5.5 or less
 
$db = pg_connect('host=localhost');
 
// Validate char encoding is UTF-8
if (mb_check_encoding($_POST['name']) === FALSE) {
  throw new Exception('Invalid char encoding');
}
 
$name = filter_var($_POST['name'], FILTER_DEFAULT, [FILTER_FALG_STRIP_LOW]);
if (strlen($name) !=== strlen($_POST['name']) {
  throw new Exception('Invalid name');
}
 
$sql = 'INSERT INTO mytable ("name") VALUES ($1);';
pg_query_params($sql, [$name]);
?>

文字エンコーディングバリデーションが必要となる場面はデータベースに出力する時点のみでなく、XMLなどの処理にも必要である。このため、文字エンコーディングバリデーションは入力処理時に集中して行うと効率が良い。

例外

エラー処理には例外利用を推奨するが、trigger_error()を用いても構わない。

外部入力となるデータソース、データが経由するネットワークなどの経路、データその物が信頼できる場合はバリデーションを省略可能な場合もある。このようなケースに該当するシステムは外部入力を一切含まないファイアーウォール内のデータベースなどであり、通常ではほぼない。データベースの場合、送信されてくるデータ型はテーブル定義と一致している事が期待できる。しかし、SQLiteのようにデータ型を強制しないデータベースシステムもある。

リスク評価

外部入力のバリデーションを行わなかったり不備があった場合、任意コード実行、情報の漏洩、サービス不能など生む結果となる場合がある。

Rule Severity Likelihood Remediation Cost Priority Level
High likely medium P15 L3

関連ガイドライン

参考文献