id:t_komuraさんの、最新の PHP スナップショットでの htmlspecialchars()/htmlentities() の修正内容についてを読みました。
見ていて気になったことが1つあります。
2. EUC-JP …(省略)… (2) \x80 - \x8d, \x90 - \xa0, \xff については、そのまま出力される <?php var_dump( bin2hex( htmlspecialchars( "\x80", ENT_QUOTES, "EUC-JP" ) ) ); var_dump( bin2hex( htmlspecialchars( "\x8d", ENT_QUOTES, "EUC-JP" ) ) ); var_dump( bin2hex( htmlspecialchars( "\x90", ENT_QUOTES, "EUC-JP" ) ) ); var_dump( bin2hex( htmlspecialchars( "\xa0", ENT_QUOTES, "EUC-JP" ) ) ); var_dump( bin2hex( htmlspecialchars( "\xff", ENT_QUOTES, "EUC-JP" ) ) ); PHP 5.3.0 / PHP 5.2.11 の結果です。 string(2) "80" string(2) "8d" string(2) "90" string(2) "a0" string(2) "ff" 修正後(最新のスナップショット)の結果です。これらの文字列については変更ありません。 string(2) "80" string(2) "8d" string(2) "90" string(2) "a0" string(2) "ff"
この挙動は、一部のブラウザにおいて問題になると思われます。
というのも、実は一部のブラウザにおいては、本来のEUC-JPの先行バイトである「\x8e, \x8f, \xa1 - \xfe」以外のバイトでも、後続の文字を食いつぶす現象が発生するからです。
少し古い情報ですが、Bypassing script filters via variable-width encodingsに、ブラウザごとに後続の文字を食いつぶすバイトが書かれています。
+-----------+-----------+-----------+-----------+ | | IE | FF | OP | +-----------+-----------+-----------+-----------+ | UTF-8 | 0xC0-0xFF | none | none | +-----------+-----------+-----------+-----------+ | GB2312 | 0x81-0xFE | none | 0x81-0xFE | +-----------+-----------+-----------+-----------+ | GB18030 | none | none | 0x81-0xFE | +-----------+-----------+-----------+-----------+ | BIG5 | 0x81-0xFE | none | 0x81-0xFE | +-----------+-----------+-----------+-----------+ | EUC-KR | 0x81-0xFE | none | 0x81-0xFE | +-----------+-----------+-----------+-----------+ | EUC-JP | 0x81-0x8D | 0x8F | 0x8E | | | 0x8F-0x9F | | 0x8F | | | 0xA1-0xFE | | 0xA1-0xFE | +-----------+-----------+-----------+-----------+ | SHIFT_JIS | 0x81-0x9F | 0x81-0x9F | 0x81-0x9F | | | 0xE0-0xFC | 0xE0-0xFC | 0xE0-0xFC | +-----------+-----------+-----------+-----------+
上表の赤字箇所をみると、IE(IE6)はEUC-JPの「\x8e, \x8f, \xa1 - \xfe」以外のバイトである「\x81 - \x8d, \x90 - \x9f」でも後続の文字を食いつぶすことが分かります。上の表は2006年の情報で少々古いのですが、私が3〜4ヶ月前にIE6/IE7で試した時も、上表と同じ結果になりました。
ですので、IE&EUC-JPの場合、id:t_komuraさんの日記で説明されている最新版のhtmlspecialchars()でエスケープを行ったとしても、後続の文字を食いつぶす攻撃を防げない(場合がある)、ということになります。
****
ところで、上の現象が起こるのはIEだけです。IEでそのような現象が起こる理由は、本題ではないので割愛します。また、IE側が異常(脆弱)であるという捉え方もありますが、ここではひとまずIE側の話はおいておきます。
htmlspecialchars()側としてどうなのかを考えると、どうせ文字チェックをするのであれば「\x8e, \x8f, \xa1 - \xfe」だけをはじくという、ある種のブラックリスト的な方法ではなく、いっそのこと「EUC-JPとして正常なシーケンスしか出力しない」というホワイトリスト的なアプローチをとることができないものだろうかと思います。
ホワイトリスト的な方法にもいくつかのレベルがありますが、比較的シンプルなのは、以下のようなラフな正規表現(Perl風)にマッチしないものをinvalidだとみなす方法です。
\A([\x00-\x7f]|(\x8F?[\xA1-\xFE]|\x8E)[\xA1-\xFE])*\Z
さらにホワイトリスト的なアプローチを突き詰めると、文字集合の話が出てきます。例えば、機種依存文字やユーザ定義文字(絵文字など)をどうするか、あるいはIEが解釈できない補助漢字などの3バイト文字をどうするかのような話です。
理想を言えば、htmlspecialchars()が、通常のEUC-JPに加えて、一般的に使われる亜種コード(eucjp-win, CP51932)もサポートした上で、文字集合のチェックまで実施するのが望ましいと思います(PHPマニュアルによると、現状のhtmlspecialchars()は、eucjp-winやCP51932といった文字コードをサポートしていません)。