2012/12/20

エンコーディングの切り替えによるSelf-XSSを考える

以前、こんなXSSの問題がありました。

第6回 先行バイトの埋め込み:本当は怖い文字コードの話|gihyo.jp … 技術評論社
http://gihyo.jp/admin/serial/01/charcode/0006

@hasegawayosuke さんの書いた記事ですね!
ウェブアプリケーションでユーザーが文字を出力できる部分で、マルチバイト文字の先頭にあたるバイト値を与えることで、後続の任意の文字と合わさって、1つの文字として解釈されてしまい、本来のページ構造を破壊してXSSを起こせる場合があるという問題です。
 この問題は、最近まで存在していましたが、現在はブラウザ側で配慮されて、最新のブラウザの広く使われているほとんどのエンコーディングにおいて、エンコーディングとして成立しないようなバイト列が現れた場合は、後続の文字を侵食しないようになっています。

今日は、この問題が解決されてもまだ、エンコーディングにまつわるXSSの問題は全てのブラウザのメジャーなエンコーディングのページで発生しうる、という話をします。特段新しい話ではありませんが、あまり広く話題にあがったこともないと思うので、一度書いてみます。

エンコーディングの切り替えによるセルフXSS

以下のようなページがあったとします。

q="><script>alert(1)</script>\%E3
http://vulnerabledoma.in/selfxss_vulnerable_js?q=%22%3E%3Cscript%3Ealert(1)%3C/script%3E%5C%E3
<script>
var q="\"&gt;&lt;script&gt;alert(1)&lt;/script&gt;\\�"
</script>

「q」パラメータの値が、JavaScriptの文字列リテラルに入るようになっているページです。このページはUTF-8で作られており、みてわかるとおり最低限のXSS対策ができています。というか、最低限どころか、不正なバイトが含まれた場合はU+FFFDにするような処理がアプリケーション側でしてあり、古いブラウザが使用されたとしても、最初に書いたような後続のバイトを侵食することによるXSSが起きないよう配慮してあります。

しかし、もしここに、

(1) 以下のような文字列を与え、

q=く";alert(1)//
http://vulnerabledoma.in/selfxss_vulnerable_js?q=%E3%81%8F%22%3Balert(1)//

<script>
var q="く\";alert(1)//"
</script>

(2) エンコーディングをShift_JISに手動で切り替えた場合、

ほとんどのブラウザ(Firefox/Opera/Chrome/Safari/IEなど)でアラートのスクリプトが動作してしまいます。なぜなら、Shift_JISとしてページを解釈した場合、文字列リテラル中の文字が以下のようになるためです。

q=く";alert(1)//
http://vulnerabledoma.in/selfxss_vulnerable_js?q=%E3%81%8F%22%3Balert(1)//
<script>
var q="縺十";alert(1)//"
</script>

文字が変わった部分を抜き出してみてみましょう。

UTF-8の解釈Shift_JISの解釈
\
0xE3818F0x5C0xE3810x8F5C


Shift_JISにすると、UTF-8でひらがなの「く」を構成する「0xE3818F」の先頭2バイトが、まず「縺(0xE381)」という文字と解釈され、残った「0x8F」が、後続の、二重引用符のエスケープシーケンスである「\(0x5C)」と1つの文字、「十(0x8F5C)」を作ってしまうのです。これにより、エスケープシーケンスがなくなった二重引用符は、そこで文字列リテラルを閉じることになります。こうして、その後ろに書かれたアラートが動いてしまうという訳です。

つまりこれは、攻撃者に誘導され、ユーザー自身が文字エンコーディングを切り替えた場合、本来XSSがないページでもXSSが起こせる場合があるということです。
IE以外のブラウザは、親フレームのエンコーディングを変更した場合、クロスドメインでも子フレームのエンコーディングまで変更するので、こんな誘導の仕方もできます。

http://l0.cm/selfxss_frame_sample/


UTF-8/Shift_JIS どちらにとっても完全に成立するバイトのみで構成されていた場合、もしエンコーディングを切りかえられたら、先行バイト問題に対処したブラウザでも、ブラウザ側で配慮する余地はありません。

 現在は、アドレスバーでのJavaScriptの実行が制限されるなど、ブラウザ側で"ユーザー自身の操作によるXSS(セルフXSS)"も防いでいこうという動きがあります。が、エンコーディング切り替えによるセルフXSSをブラウザ側で対策するのは、ユーザーによるエンコーディングの選択を不能にすることくらいしかできないでしょう。でもそれをすると、文字化けが起きたとき、ユーザーがページを正しいエンコーディングに直すことが難しくなってしまいます。
問題解決に積極的な2つのブラウザベンダにはこの問題を通知しましたが、どちらも「どうしようもないね」というかんじです。

さあ、どうしよう。アプリケーション開発者にとって、ユーザーが文字コードを切り替えて起きる問題なんて知ったこっちゃないというのも正論なんですが、ユーザーにとっても、文字コードを切り替えただけでセキュリティ問題が起きるなんて知ったこっちゃないというのもまた同意できると思います。

アプリケーション側の対策

そんな訳で、アプリケーション側で対策する方法を考えてみました。現在一番よく使われるエンコーディングの、UTF-8のページで、文字列リテラル以外で出力される場合や、Shift_JIS以外のエンコーディングに切り替えた場合に成立する攻撃にも対応できる防御を考えます。今回は他のエンコーディングを用いた具体的な攻撃方法については触れませんが、選択可能なエンコーディングの中には、あるバイト値(あるいはバイト列)を開始点として、終点を示すバイト値(あるいはバイト列)まで0x00-0x7Fの範囲の文字でさえ漢字などに化かしたりする規則のもの(HZ-GB-2312やISO-2022-*など)もあり、一筋縄ではいきません。

現実的にすぐに対応できるのは、一つずつ個別のエンコーディングのことを考えたり、問題が起きるとわかっているページだけ手をうつことよりも、UTF-8以外で表示が行われそうになった場合、表示を停止するようなJavaScriptをページの頭(少なくともユーザー入力を出力するところより前)に書いておくことだと思います。

以下はそのサンプルです。スクリプトが無効の場合でも偽のフォームを出現させたりすれば攻撃は可能でしょうが、今回はひとまず影響が大きいスクリプトが有効の場合のみを考えました。以下でエンコーディングをShift_JISに切り替えて、問題が起きないことを確認してみてください。

q=く";alert(1)//
http://vulnerabledoma.in/selfxss_safe_js?q=%E3%81%8F%22%3Balert(1)//


JavaScriptでエンコーディングがUTF-8かどうかを判定しています。UTF-8以外で表示されようとすると、plaintextタグでそれ以下を無効にします。plaintextタグは閉じられないので、仮にそれ以下に</plaintext>という文字列が現れても問題ありません。

これでほとんどカバーできるはずですが、選択可能なエンコーディングの中で、Chrome/Firefox/Operaで選択できる「UTF-16」だけはこのJavaScriptでは防げません。UTF-16では、2バイトの、1つか2つの連続で1文字を表現するので、防御用のJavaScriptが完全に別の文字に置き換えられ、動作しなくなってしまうためです。
 このページでは、文字列リテラルで「<>」を文字参照に置換しているので問題にならないのですが、「<>」を処理する代わりに「/」を「\/」と出力してXSSに対処しているページでは、そこに[0x00]のバイト値が挿入できるなら、UTF-16が選択された時XSSが起きてしまいます。
イメージとしては以下のようなかんじです。UTF-16(BE/LE)に切り替えてみてください。

(※「% 0 0」を繋げて書き込むと除去されるBloggerのバグを回避するため、リダイレクタを挟んであります。)
http://vulnerabledoma.in/redirect/selfxss_utf16.html

ですので、[0x00]を\u0000などとするか、UTF-16LE/BEにしたときにplaintextタグが現れるよう、間に[0x00]を挟んだものをコメントアウトしておくなどして回避する必要があります。先ほどの防御の例にも一応コメントアウトして、[0x00]が間にはさまれたplaintextタグが含んであるので、UTF-16に切りかえてplaintextタグが現れることを見てみてください。


その他UTF-32など、ASCIIと著しく互換性がないエンコーディングは、少なくとも5大ブラウザでは(解釈できるものはあっても)ブラウザメニューから選択できないので考慮しなくても大丈夫そうです。

本当は止めるだけでなく、ページを正しいエンコーディングに回復させるのが理想的なんですが、 エンコーディングをユーザーから切り替えられた時のブラウザごとの挙動がバラバラなため、事実上不可能だと思います。僕が試した限りで、ユーザーが自ら指定したエンコーディングをサイト側から上書きできないブラウザもあったので、厳しそうです。

他にも、エンコーディングを変更されてもXSSが起きないページ構造にするという方法も考えられますが、変更可能な全てのエンコーディングの規則を考慮してページ全体を構築する必要があり、かなり大変で無駄が多くなりそうです。

まとめ

よほど重要な情報を持っているところでない限り、ここまで神経質に対策する必要はないと思いますが、エンコーディングにはまだこういう問題もあるよ、ということでひとまず書いてみました。

少し前までは、アドレスバーでのJavaScriptの実行はブラウザの機能として当然のものでしたが、今やそれが攻撃に利用され、制限される時代になりました。エンコーディングの切り替えによるセルフXSSも、いつ利用されてもおかしくないと思います。

この問題に対して、何かいい解決策があるという方はぜひコメントやTwitterなどで教えてください。

1 件のコメント:

  1. How i can create other payloads in this encoding format ? Can you suggest us few examples.. Like normally we use script alert(1) script

    返信削除