XSS対策:JavaScriptのエスケープ(その4)

XSS対策:JavaScriptのエスケープ(その3) - ockeghem(徳丸浩)の日記にて、JavaScriptリテラルを動的生成する場合のエスケープ方法について検討したが、id:hoshikuzuさんから、考慮がもれているという指摘を受けた(http://d.hatena.ne.jp/hoshikuzu/20071011#p1:TITLE=2007-10-11 - hoshikuzu | star_dust の書斎 - JavaScriptエスケープについて論考)。
上記は長いエントリーではあるが、要約すると、特定文字エンコードの特定のバイト●に対して、特定ブラウザにおいて「●\」が一文字「■」として解釈されるため、「"」に対するエスケープ「\"」が破綻するという意味に解釈した。
興味深い内容であるので、以下に考察してみよう。

なぜエスケープが破綻するのか

上記のようなケースはさほど珍しくはない。私はかつて、http://pc.nikkeibp.co.jp/article/COLUMN/20070409/267808/を書いたときには、0x81によりイベントハンドラ内の「"」が「食われて」しまう可能性に言及している。また、最近広く知られるようになったUTF-7によるXSSも、広義に解釈すればこのパターンである。
XSS対策:JavaScriptのエスケープ(その3) - ockeghem(徳丸浩)の日記では、文字エンコードについての言及はない。これは、暗黙の前提として、「アプリケーションとブラウザは文字エンコードを同じように解釈・処理する」という仮定を置いていることになる。id:hoshikuzuさんの指摘した例は、この前提が崩れるケースの典型例である。id:hoshikuzuさんは言及していないが、Webアプリケーション側でもブラウザとまったく同じように文字エンコード解釈がされるのであれば、エスケープ漏れにはならない可能性が高い。なぜなら、「●」は単独バイトでは文字として不正であるわけだから、

Webアプリ : ●" というバイト列 →  ●"で一文字と解釈 → エスケープされない(バイト列としては ●" ) 
 |
 | アプリの表示をブラウザが受け取る
 ↓ 
ブラウザ : ●" というバイト列 → ●"で一文字と解釈 → アンエスケープされない (XSSは発生しない)

このような流れで処理されるからだ。ここで問題となるのは、「●"」を一文字と解釈するかどうかであって、Webアプリとブラウザで解釈が異なればXSSが発生する可能性が高い。
Webアプリあるいはブラウザのバグ、あるいは使用頻度の低い文字種などで上記のようなこと(解釈・処理の不一致)は十分起こりえる。また、XSSではないが、SQLインジェクション対策として「'」などのエスケープにより対処する場合、WebアプリあるいはSQL処理系のどちらかの日本語対応が不十分な場合に上記と同じことが起こる(これもよく知られている)。また、UTF-7を利用したXSSは、上記の流れとは少し違うが、Webアプリ側はUTF-7以外の文字コード(例えばシフトJIS)を利用しているにも関わらず、ブラウザ側がUTF-7と誤認することから、文字エンコード解釈の差異が生じると言う点では同じである。

文字エンコード問題は、XSSだけが問題ではない

文字エンコードの問題は非常にやっかいであり、ここ最近の間でもEUC-JPにまつわる問題が一部で活発に議論されている。その議論の中心の一人は、ほかならぬid:hoshikuzuさんである。そして、id:hoshikuzuさんが提示される問題*1は、もはや「出力時の」エスケープでは対応しきれないように思われる。これは、「出力時のエスケープ」の限界なのだろうか。
そうではなかろう。文字エンコードの問題は、それ単体で対応すべきである。なぜなら、文字エンコードの問題は、HTML出力(XSS)の場合に限らず、SQLインジェクションやそのほかのインジェクション系脆弱性全てに影響するからである。したがって、JavaScriptの文字列リテラルの場合には、過剰なエスケープ(全ての文字をエスケープする)が有効だとしても、SQLインジェクションには適用できない。金床さんの教科書(ウェブアプリケーションセキュリティ)には、SQLインジェクション対策の一例として「データをエンコードした形で格納する(p267)」というのが説明されているが、金床さん自身が「非常にクセのあるアプローチであるので、ここではあくまでも実験的なものとして紹介しておく」としている。そうであろう。金床さんが指摘しているように、可読性やデバッグ効率が低下するからだ。
したがって、文字エンコードの問題は、XSS単体としてとらえるのではなく、インジェクション系脆弱性全体、ひいてはアプリケーションが正しく動くための共通の課題としてとらえるのがよいと思う。

では、どう対処すればよいのか

これは中々難しい問題だが、そもそも文字エンコードの問題はアプリケーションが一々考慮しなければならない問題ではなく、ミドルウェア側で対応しておくべきものだろう。だとすれば、そろそろ入力値検証に関して一言いっとくか: Webアプリケーション脆弱性対策としての入力値検証について - 徳丸浩の日記(2007-09-05)で指摘したように、Webアプリケーションに処理がわたる前のミドルウェア側の責任として、前提とする文字エンコードとして不正な文字(バイト列)が入っていたらエラーにするなどの対応をして欲しいものである。現実のミドルウェア類はそこまで実施しているものは少ないだろうから、「Webアプリケーションの入り口」でのバリデーションとして対応するのがよいと考える。