This is an old revision of the document!
Table of Contents
外部へ出力するデータはデフォルトで全て無害化する
安全なコードを書く為には、外部(データベース、OS、クライアントなど)へ出力するデータはデフォルトで全て無害化することが必要です。
無害化しなければならないデータは「形式」を持つデータ全てです。一文字でも意味がある文字がある文字列、特定の形式を持つデータ(データの位置に意味がある構造/形式)等全てです。
出力先のデータ形式には
- 可変長テキスト - SQL/XML/HTML/メールメッセージ/XPATHクエリ/LDAPクエリなど構造化されたテキスト
- 固定長テキスト - データの位置で意味が定まる物
- 可変長バイナリ - NULL文字など制御文字で構造化されたバイナリ
- 固定長バイナリ - 画像のヘッダーなど(固定長と可変長の組み合せである場合もある)
等があります。Web環境では可変長テキストが多用されますが、無害化すべきデータは可変長テキストに限りません。構造化されてないテキストデータでも無害化が必要です。
- 文字エンコーディングバリデーション - 文字エンコーディングベースの攻撃防止に必要
文字エンコーディングのバリデーションは出力時にも行えますが、入力を受け付けた時点でバリデーションしないと問題を生む場合も少なくありません。入力バリデーションと出力時に出力を無害化する処理は独立したセキュリティ対策です。セキュアなコードを書く場合には、入力と出力の両方でセキュリティを意識したコードが必要です。
固定長/可変長のデータ(文字やデータ位置に意味があるデータ)、文字エンコーディングを持つデータは全て出力する前に無害化しなければなりません。
無害化の手段
- エスケープするか
- エスケープが必要ないAPIを利用
- バリデーション
これらの何れかの手段を出力するデータ全てに適用する。
エスケープはコンテクストに合わせて行わなければならない。例えば、SQLインジェクション対策としてエスケープ/エスケープが必要ないAPIを利用しても、LIKEクエリや正規表現をサポートするDBMSの場合、LIKEクエリのエスケープ、正規表現のエスケープが必要となる。
データ形式がスカラーであるかどうか、で振るまいが変る出力先もあります。このようなAPIを持つ出力先にはデータ型のバリデーションも欠かせません。例:MongoDBインジェクション
データ形式をバリデーションしたり、出力データをエスケープするだけでは不十分な場合も少くありません。例えば、データベースクエリで取得するレコード数は最大値を制限(LIMIT句で取得可能なレコード数の値を制限)していないと、システムが応答不能になる問題が発生する場合があります。
出力データのサイズが大きすぎないようにする必要がある場合もあります。例えば、データベースのカラムサイズに制限がある場合(普通はある)はカラムサイズを超えないデータサイズを出力しないと、整合性のないデータがデータベースに保存される場合があります。例:PostgreSQLのvarchar型は大きすぎるデータでエラーとなるが、SQLite3のvarchar型は大きさをチェックしないで保存する。
このルールに非適合となるコードは多岐に渡る。非適合コード例は一部に過ぎない。
非適合コード例(Output depends on input validation)
入力対策と出力対策は独立した対策である。出力時に無害化できるものは全て無害化する。
<?php $db = pg_connect('host=localhost'); if (!$db) { throw new Exception('Cannot connect to database'); } // There should be arbitrary length limit validation if (strspn(strlen($_GET['id']) > 100) { throw new Exception('Invalid integer format'); } // Validate $_GET['id'] contains only 0 to 9 chars if (strspn($_GET['id'], '0123456789') !== strlen($_GET['id'])) { throw new Exception('Invalid integer format'); } $id = $_GET['id']; // Usually there are many lines of codes and/or input/output // codes are stored in different files. // $id is used without escape/validation at output $sql = 'SELECT * FROM sample WHERE id = '.$id; $ret = pg_query($sql); if ($ret === FALSE) { throw new Exception('Query failed'); } ?>
適合コード例
一部のデータベースは整数などに文字リテラルをサポートしていない場合もあるが、ほとんどのデータベースはサポートしている。全てのパラメータに文字リテラルをサポートするデータベースの場合、すべて文字列としてエスケープする。
<?php $db = pg_connect('host=localhost'); if (!$db) { throw new Exception('Cannot connect to database'); } // There should be arbitrary length limit validation if (strspn(strlen($_GET['id']) > 100) { throw new Exception('Invalid integer format'); } // Validate $_GET['id'] contains only 0 to 9 chars if (strspn($_GET['id'], '0123456789') !== strlen($_GET['id'])) { throw new Exception('Invalid integer format'); } $id = $_GET['id']; // Usually there are many lines of codes and/or input/output // codes are stored in different files. // $id should be escaped/validated at output or use pg_query_params()/etc. $sql = 'SELECT * FROM sample WHERE id = '.pg_escape_literal($id); $ret = pg_query($sql); if ($ret === FALSE) { throw new Exception('Query failed'); } ?> PostgreSQLの場合、pg_query_params()などのプリペアードクエリを利用しても良い。 識別子(テーブル名やカラム名)がパラメータの場合、PostgreSQLではpg_escape_identifier()が利用できる。識別子のエスケープ関数がないAPIを利用している場合、データベースの識別子エスケープ仕様に合わせて関数を作成し適用しなければななあい。LIKEクエリや正規表現を利用したクエリは、SQLコンテクストとは異る。必要な場合はLIKEクエリ、正規表現用のエスケープ関数を使ってエスケープしなければならない。 ===== 非適合コード例(SQLite3 varchar column) ===== SQLite3のvarchar型は長さのバリデーションを行わない。 <code php> <?php $db = new PDO('sqlite:/tmp/mydb'); // Test table $db->query('CREATE TABLE test (str varchar(8));'); // Test data $str = 'aaaaaaaaaaaaaaaaaaaaa'; $db->query("INSERT INTO test (str) VALUES (".$db->quote($str).");"); ?>
sqlite3コマンドでmydbの内容を確認すると以下のような出力となる。
sqlite> select * from test; aaaaaaaaaaaaaaaaaaaaa
適合コード例(Validation)
<?php $db = new PDO('sqlite:/tmp/mydb'); // Test table $db->query('CREATE TABLE test (str varchar(8));'); // Test data $str = 'aaaaaaaaaaaaaaaaaaaaa'; // PHP 5.5 or less requires encoding setting. if (!is_scalar($str) || !mb_check_encoding($str) || strlen($str) > 8) { throw new Exception('Invalid str'); } $db->query("INSERT INTO test (str) VALUES (".$db->quote($str).");"); ?>
例外
文字エンコーディングのチェックは入力受付時に行う方が効率的である。全ての入力がバリデーションされている場合、出力時の文字エンコーディングバリデーションは省略しても構わない。
出力パラメータには無害化ができない物(HTML構文交じりのパラメータなど)もある。この場合、無害化できないパラメータを生成する場合に、全てのパラメータを無害化する。
リスク評価
出力対策と入力対策は独立した対策だが、入力対策だけで十分とするコードは間違いを起こしやすい。複雑なソフトウェアでは全てのパラメータが十分に安全なバリデーションを適用されているかを保証することも困難である。非常に単純なコード以外では出力時に全て無害化する。
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| IDS05-J | critical | likely | medium | P4 | L3 |
