出力文字エンコーディングのバリデーション

Security 9月 28, 2009
(Last Updated On: 2013年11月29日)

前のエントリで「書かない日記」の名前通り、出力文字エンコーディングのバリデーションについてあまりに書かなさすぎで何の事やら分からない方も居たと思います。もう少し詳しく書きます。出力バッファとエラーハンドラで出力文字エンコーディングを簡単にバリデーションできます。

PHPで出力文字エンコーディングをバリデーション

ステップ1: エラーハンドラを登録

function myErrorHandler($errno, $errstr, $errfile, $errline) {   
// 出力バッファをクリア - header()で送信したヘッダもクリア
ob_end_clean();

// エラーメッセージ - もちろんエラーの種類に合わせたHTMLでも良い
echo "Some thing goes wrong.";

// 本来はエラーログを取るべきですが省略

exit; // 全てのエラーでスクリプトの実行は停止すべき
}

set_error_handler('myErrorHandler'); // ハンドラを登録

ステップ2: 出力バッファハンドラを登録

function myOutputHandler($output) {
// 実用コードではContent-Typeやスクリプトのチェックが必用

if (!mb_check_encoding($output)) { // 文字エンコーディングが不正
trigger_error('Invalid char encoding', E_USER_ERROR);
exit; // 念のため
}
return $output; // OK
}

ob_start('myOutputHandler'); // ハンドラを登録してバッファ開始
// デフォルトで全部バッファリング

 

前提事項など

  • ハンドラの登録は出力が始まる前に行う
  • php.iniのmbstring.mb_internal_encodingで文字エンコーディングが設定されている
  • アプリの中でエラーハンドラが登録されていない(エラーでバッファクリア&スクリプト停止となるハンドラならそのまま利用可能)
  • アプリの中で出力バッファにob_gz_handler(圧縮ハンドラ)が登録されない
  • アプリがマルチバイト文字の途中で切り捨てるような動作をしない
  • データベースなど既にある出力用データの文字エンコーディングが正しい
  • テキスト以外を送る場合、Content-Type/スクリプト名等でエンコーディングチェックの有無を指定するコードが必用

PHP4/5、両方で利用できるはずです。しかし、ドキュメントされていない様々な落とし穴があるのでPHPはできるかぎり最新版を使いましょう。

PHP4では送信済みのヘッダを取得するhaders_list()関数が利用できません。したがってContent-Typeは判別できません。しかし、テキスト以外を送信するスクリプトはそれほど多くないはずです。ホワイトリストで除外すると良いです。

アプリによっては他の注意事項もあるかも知れませんし、コードはブログに直接記入した非常に単純なサンプルコードです。適宜必用なカスタマイズを行って利用してください。今動作しているアプリでも動作しなくなるケースも少なくないでしょう。必用なログコードの追加やコンテンツのチェックの追加等を行い、十分テストしてから利用してください。

しかし、ある程度アプリの中身さえ知っていればphp.iniのauto_prepend_file設定などでエラーハンドラと出力バッファハンドラを登録するスクリプトを読み込んで、アプリ本体のコードに手を加えず出力時にバリデーションできるようになるアプリケーションは多くあります。

ETagの値を取得するために出力全体を保存しているRailsでも、同じような仕組みにする事は比較的簡単だと思います。ETag生成のコードで「たまたま」例外が発生するようなコードだと危うく感じます。

出力時だけバリデーションするとDoS攻撃に脆弱になります。入力と出力、両方でチェックし、出力時のチェックはフェイルセーフ対策とすると万全でしょう。

 

言語の文字列関数などによる自動チェック

文字列関数などによる文字エンコーディングの自動チェックは不必要、とまでは言いませんが在ったら便利程度の機能(フェイルセーフ機能)と思っています。※ どのような状態になれば「出力する文字列が安全か?」、入力でも出力でもなく、プログラムの処理の中で一つ一つチェックして安全性を確認する事と入力・出力時に確認して安全である事を確認する事、どちらが簡単かは説明の必用も無いと思います。

安全性のためには無駄は受け入れるべきという考え方ですが、全ての文字列操作で文字エンコーディングのバリデーションを行い、文字列を繰り返しバリデーションする仕様には無駄が非常に多いとも感じています。実際、無駄が非常に大きいので全ての文字列操作で文字エンコーディングのバリデーションが行われるようにはならないでしょう。仮に全ての文字列操作でバリデーションしたとしても、プログラミング言語はバイナリも取り扱います。銃に安全装置を付けてもShooting your own footのような行為は防げません。

プログラマが入力と出力をしっかり制御していれば、攻撃可能な脆弱性を作ってしまう可能性は大きく低下します。自動チェックなど基本的にはあてにすべきではありません。

OWASPではESAPI (Enterprise Security API)の開発が進められています。このAPIも私の考えと全く同じ理由とセキュリティ維持のコンセプトで開発されています。

ESAPIのコンセプトは良いのですが、言語によっては実装がイマイチな部分があります。しかし、時間と共に改善すると思います。このようなAPIが一般的になれば随分セキュリティは向上すると思います。

 

※なので、Ruby 1.8, Perl, Python3未満,PHP6未満がこの点で言語仕様的に大きく劣っているとは思っていません。

 

投稿者: yohgaki