| « 詐欺かな - 「出会い系サイトを運営しませんか?」メール | Dojo Javascript Toolkit » |
これからのプログラムの作り方 - 文字エンコーディング検証は必須
最近PostgreSQL、MySQL両方にSJISエンコーディングを利用している際のエスケープ方法の問題を修正がリリースされています。この件は単純に「データベースシステムにセキュリティ上の脆弱性があった」と言う問題ではなく「アプリケーションの作り方を変える必要性」を提起した問題です。
PostgreSQL、MySQLの脆弱性は特にSJIS等、マルチバイト文字に\が含まれる文字エンコーディングが大きな影響を受けますが、同類の不正な文字エンコーディングを利用した攻撃方法が他の文字エンコーディングでも可能です。例えば、UTF-8エンコーディングは1文字を構成するバイト列の最初のバイトの何ビット目までが1であるか、を取得してUTF-8文字として1バイト~6バイト必要なのかわかる様になっています。アプリケーションやライブラリによってUTF-8文字列の解釈の方法が異なると問題が発生する可能性があります。最も単純な例としてはUTF-8文字の中にASCIIの特殊文字を埋め込む方法が考えられます。この壊れたUTF-8文字列をASCIIとして処理してしまうと様々な問題が発生してしまうことは容易に想像できます。
今後は全てのセキュアなアプリケーション、ライブラリで文字エンコーディングが妥当であるかチェックすべきです。壊れた文字がある場合は削除してはいけません。安易な他の文字への変換も脆弱性を生む場合があります。
これからプログラマは常識として以下の項目を実践すべきと思います。
- 文字エンコーディングが妥当であるか必ず入力検査時にチェックする
- 壊れた文字エンコーディングを見つけた場合はエラーにして処理を中止する(当然エラーを記録する)
- 壊れた文字は「絶対」に削除しない
- 壊れた文字を置き換える場合は安全性に十分注意する(非推奨)
- システムで利用する文字エンコーディングはできるだけ統一する(努力目標)
PHP 5.1.4, PHP 4.4.3からはmb_check_encoding関数が文字エンコーディングの妥当性をチェックするための関数として追加されました。この関数は配列を引数に取れないのでarray_walk関数などと一緒に利用して$_GET,$_POST,$_COOKIE,$_SERVER等の値をチェックします。PHP5.1.4, PHP 4.4.3より古いPHPではmb_convert_encoding関数を使用し、変換前と変換後の文字エンコーディングに同じ文字エンコーディングを指定し、変換前と変換後の文字列に変化があるかを確かめる事により不正な文字エンコーディングを検出できます。
(注:PHP 4.4.3は現時点ではリリースされていません。そして、今mb_check_encoding()はマニュアルに記載されていないことにも気が付きました。(汗 これくらいは自分ですべきと思いますが時間が... 申し訳ないです > 関係先各位 )
Cなどのアプリケーションやライブラリの場合、libiconvを使えば不正なエンコーディングをerrnoを利用してチェックする事が可能です。(PHPの場合もiconvモジュールを使って同様のチェックも行えます。mbstringが利用できない場合はiconvモジュールを利用すると良いでしょう)
以前から文字化け対策としてよく言われていた事ですが、セキュリティ面からもシステムとして利用する文字エンコーディングはできる限り統一した方が良いです。UTF-8に統一する事が多くなると思いますが、UTF-8は正規化により他の文字エンコーディングに無い脆弱性を作ってしまう場合があります。(特にIDS/IPS関連)蛇足ですが、UTF-8を利用する場合は必ずアプリケーション側で全てのデコード処理が終わった後に入力が期待する入力かチェックします。
ところでPostgreSQL 8.1.4のlibpqからPQescapeStringが非マルチスレッドセーフになりました。この影響により複数の文字エンコーディングを利用しているマルチスレッド環境(PHPのWindows版 - Apache, IIS両方がスレッド型Webサーバであるため影響を受けます)では問題が発生する可能性があります。単独でサーバを管理している場合は問題が発生することはあまりないと思いますが、Windows環境でWebサーバを複数の利用者で共有している場合は影響があるので注意が必要です。
補足:
PHPでの文字エンコーディングチェックのサンプルコード(mb_check_ecodingの有無もチェックしています)
// expected char encoding
define('APP_ENCODING', mb_internal_encoding());
// define call back function for array_walk()
if (function_exists('mb_check_encoding')) {
// mb_check_encoding exists. just use it
function encoding_check($val, $key, $encoding) {
if (is_array($val)) {
array_walk($val, 'encoding_check', $encoding);
} else {
if (!mb_check_encoding($val, $encoding)) {
trigger_error('Encoding attack. ', E_USER_ERROR);
exit;
}
}
if (!mb_check_encoding($key, $encoding)) {
trigger_error('Encoding attack. ', E_USER_ERROR);
exit;
}
return true;
}
} else {
// mb_check_encoding does not exist. use mb_convert_encoding()
function encoding_check($val, $key, $encoding) {
if (is_array($val)) {
array_walk($val, 'encoding_check', $encoding);
} else {
$val2 = mb_convert_encoding($val, $encoding, $encoding);
if (!($val2 === $val)) {
trigger_error('Encoding attack. ', E_USER_ERROR);
exit;
}
}
$key2 = mb_convert_encoding($key, $encoding, $encoding);
if (!($key2 === $key)) {
trigger_error('Encoding attack. ', E_USER_ERROR);
exit;
}
return true;
}
}
$inputs = array($_GET, $_POST, $_COOKIE, $_REQUEST, $_SERVER);
foreach($inputs as $input) {
array_walk($input, 'encoding_check', APP_ENCODING);
}
Unicode(UTF)の場合、BOM、正規化などの問題の為意図した結果にならない場合があります。 EUC、SJISの場合は特に問題ないと思います。EUC-JPよりはEUCJP-win、SJISよりはSJIS-winをエンコーディング名に指定した方が問題が発生しづらいかも知れません。
4 comments
mb_check_encoding($str, 'auto')が通るのはバグというより仕様かな? passは無意味なのでエラーで良いと思います。自分は使わないと思いますがautoはエンコーディングリストの順番に検証してOKかNGか知りたい、というケースはあるでしょう。多分。
日本語のWebアプリケーションには壊れた文字エンコーディングで致命的なセキュリティ問題を引き起こす場合が多くある為、ある程度準備が整うまではあまり広く公開できないのが困ったものです。この件はPostgreSQLプロジェクトの方でCVEが作成されていて対処には結構時間をかけて行っていました。詳しくは石井さんのITProの記事を参考にしてください。
それからiconvは実装によって異なるのは有名(?)です。使う場合はその辺りを理解してから使った方が良いと思います。
Unicodeの問題には正規化の問題もあったり、UTFには同じ文字でも別の表記(エンコーディング)ができる問題、BOM(Byte Order Mark)の問題、Endianの問題などがあるので結構面倒です。というよりこんな仕様にした(なった)のは...と言いたくなります が、仕方無いですね。この為にmb_check_encoding()を追加する必要もあったと言えます。文字コード・文字エンコーディングは勝手にいろいろしてしまう携帯キャリアもあったり、何でもありの世界になっていますね。
POSTに空文字があった場合、
mb_convert_encoding($val, $encoding, $encoding);
がfalseになりtrigger_errorしてしまわないでしょうか。フォームで入力必須でないテキストフィールドを使う場合にと。
空の文字列であっても正しい文字エンコーディングなので問題は発生しないはずです。何か問題が発生しているのでしょうか? mbstringモジュール自体がロードされているかチェックしていないのでmb_check_encodingが無いとエラーになります。
いつでもコレが使えるかというとバイナリをURLエンコードしている場合などでは使えません。BASE64等を利用するか、除外できるようにカスタマイズが必要です。
このバージョンあたりのバグを含めて見直してみます。