JavaScriptエスケープについて論考

はじめに

次のような限定されたケースにおいてなのですが。説明上の都合でこれを課題Aと呼ぶこととします。

<SCRIPT TYPE="text/javascript">
<!--
var strA = "$data";
// ・・・以下サイト運営者による処理記述例
alert(0);
//-->
</SCRIPT>
上記のようなケースに限定してのオハナシですけれど、$dataをエスケープする方向でのXSS対策として金床さんなどによってかつて論議されて、このままでは使えそうにないと棄却されたJavaScriptエスケープ方法が以下にまとまっています。

※手元のIE6で上のアーカイブを参照したら、みごとに\が脱落していてエスケープになっていないのは内緒だ。なので以下に引用。


1. 「\」を「\\」に置換する
2. 「"」を「\"」に置換する
3. 「'」を「\'」に置換する
4. 「/」を「\/」に置換する
5. 「<」を「\x3c」に置換する
6. 「>」を「\x3e」に置換する
7. 「0x0D(CR)」を「\r」に置換する
8. 「0x0A(LF)」を「\n」に置換する

ところが徳丸さんのエスケープ方法では上記のうち、5番、7番、8番が欠けています。

状況によりますが、これではXSS脆弱性を防止できないケースがありますので、具体的に論考してみたいと思います。また、金床さんの著書である「ウェブアプリケーションセキュリティ」においての、上記の1番から8番までを含んでいる、このJavaScriptエスケープ方法についての解説記述に、若干ですが私見を補足しておきたく思います。
なお、金床さんは、本エスケープ方法は勧めていません。

序論(大事なこと)

これからお話することは、上記に書いた課題Aの事例に関する場合に限定での論考となります。けして、万能なXSS対策について論じているのではないことにご留意ください。script要素内でユーザ出力を表現するにあたっての一般論などは、かなり難しいことになるからです。また、課題Aの事例に対して私が示す複数のXSS対策を他の事例で適用していたにもかかわらず立派なXSS穴があいていた、ということも実際にありうります。(過去にマイクロソフト社のサイトに変型版でですが本当にセキュリティーホールがありました。)

序論2

script要素内でユーザ文字列出力を表現することは、セキュリティ上大きなリスクを伴います。従って充分に経験を積んだウェブサイト構築のプロでも神経を細やかに使っているはずです。徹底的かつ精緻なホワイトリストを通過したユーザ文字列のみを適切な形で出力すべきです。いったんはセキュアな構築ができたとしても、その後のウッカリしたシステム増築によって穴があいちゃったりすることももあるでしょう。つまらない1行を書いたおかげでとか…
また、アクセシビリティの問題にも留意を願いたいと思います。一例をあげれば、課題AのXSS問題をクリアできたとして、はたしてそれで良いのかという疑問を感じてほしいのです。JavaScriptの効果を体験できない人々が世の中には少なからずいます。また、セキュリティ上の危険を憂慮してJavaScriptオフのブラウザを常用している人へのサービス停止になっているかもしれません。大事なことです。

XSS対策1

script要素内でユーザ文字列出力を一切行わない。
これはこれで立派な対策です。アクセシビリティに富み、なおかつ正確なHTML,XHTMLをブラウザに提供することはただでさえ至難な業です。ですけれどもウェブサイトとして提供したいすべての情報を提供可能なシステムをこの方針で構築することはとても良いことです。この場合、JavaScriptオフであっても提供されるべきすべての情報にブラウザからアクセスできるはずです。なお、当然のことながら、stylesheetのの援用で比較的リッチなブラウジング提供が可能でしょう。
この時にJavaScriptの出番を考えますと、既にすべての情報を提供可能である上に、さらに、よりリッチな情報提供の手段を平行して提供することにつきます。ここのところが実は大変な工数がかかるところです。script要素内でユーザ文字列出力を一切行わなうようなシステムを、JavaScriptオフでも閲覧可能なシステムの上に、さらに構築する経済的なゆとりは、もちろん大歓迎です。ですが、ウェブサイト構築の工数面から考えた場合に、「script要素内でユーザ文字列出力を一切行わない」ことは大きなメリットとなりえます。また、style記述をJavaScriptで動的に変える便宜性の提供はおおいに首肯できますが、前提となるアクセシビリティあってのことですね。

XSS対策2

アクセシビリティを保障しつつも、JavaScriptによって課題Aのような部分を含むシステムを構築したい場合について以下のように考えます。
絶対的な条件ですが、$dataをブラウザに提供する前にサーバ側で完璧なホワイトリストを通過させるべきです。また、$dataを使い終わった直後にstrAの中身をクリアしてしまう工夫が求められます。サイト構築担当者が将来チェンジした場合にポンツクなXSSなどの副作用が出ないように保険をかけておくべきでしょう。グローバル変数は実にこわいものですから。
金床さんも同様の主張を著書「ウェブアプリケーションセキュリティ」の主要な第一の骨子としています。おおいに推奨というところでしょう。

XSS対策3

さしつかえない範囲でブラックリストの通過ですみそうだと責任をもって判断した場合。
ブラックリストを通過後、サーバから提供される$dataの中身は、UTF-16BEのコードを使って、\uXXXX の形で一文字づつ、全部の文字にわたって表現すべきです。本稿では以下、この方法を過激なエスケープと通称することにします。
過激なエスケープによれば、課題Aの

    var strA = "$data";
この1ステートメントについてXSS脆弱性が発生することはないものと考えて結構かと存じます。
金床さんがJavaScriptにおける過激なエスケープを適用すべきケースを多々、同著書において示されているとおりです。
仮に特定のブラウザにおいてXSS脆弱性が発生するならば、それは、そのブラウザが完全に腐っているからです。ウェブアプリケーション側の責任ではありません。逆にそんなブラウザがあったとしたならば、ウェブアプリケーション側で対策を取れるかどうか極めて難しいことでしょう。
また、本稿を書いている主題となっている、課題Aに対する8項目にわたるJavaScriptエスケープの方法よりも、この過激なエスケープの方が優秀です。本稿後半でも述べますが、ロケールやcharsetによらずに安心して課題Aについてはセキュアであると断言できるからです。ウェブアプリケーションを作成する以上は、国際的に通用し、他国の技術者と簡単に意思疎通が可能なような、そんなセキュリティ対策をとるべきでしょう。UTF-16BEのコードでの\uXXXX の形での表記は、ロケールやcharsetによらずに通用する優れた手段です。ひょっとしたら日本特有の手段?、あるいは特定のcharsetによるかもしれないかもしれない?、あるいは、ブラウザの実装しだいで穴があるかもしれない?そんな対策はとるべきではないと思われます。

訂正:なお、明白なことですが、今回話題にしているところの課題Aに対する、くだんの8項目にわたるJavaScriptエスケープの方法にミスがあるとか、そういうことを主張しているわけではありません。あるいはミスがないとも、私の力では証明できません。は穴があります。

※読者の一部を想定してまたもやくどくどと強調しておきたく思います。課題Aの、この1ステートメントへのXSSアタックあるいはその防衛について論じているのです。そして、この1ステートメントについて仮に防衛できていたとしても、継続する変な記述のステートメントにおいてXSSアタックが成功してしまうかもしれません。セキュアなウェブアプリケーションを作成する技術者は、経験豊富であって、そのへんを実によく細かくわきまえて対策をしています。

ここでXSS対策についていったん中断します

次からは徳丸さんによる5番7番8番が抜けているJavaScriptエスケープの方法ではXSS脆弱性が発生するケースがあることを順々に検証していきます。また、その論考を通じて、「誤解され矮小化されたJavaScriptエスケープ」よりも、「過激なエスケープ」を取るべき、ひとつの理由を提示したいと思います。

編集しているあいだに気がつきました。そもそも5番7番8番があっても脆弱でした。まことにすみません。

まずは、アタック開始です。私の思考の癖が滲み出ているのでお恥ずかしい限りなのですけれど。

課題Aの攻略方法についての、ひとつの方向

多数あるかもしれないアタックルートの中でひとつだけ考えることとします。

$data が、攻撃者によって、ひとつのアタックルートとして次の形式になっていることを想定します。

$data1;$data2;$data3

$dataの中でセミコロンで分断されている$data2;の部分が、攻撃者によるスクリプト挿入部分であって、しかも、課題Aにおける$dataを含むステートメント全体においてスクリプトエラーが発生しないこと、これが攻撃者の目標です。もしもスクリプトエラーが発生すれば、XSSアタックが成功しませんので。

まずは、$data3の部分の研究をしましょう。このことで、徳丸さんによる、5番が欠けているJavaScriptエスケープの方法の穴に対する、ひとつの登攀ルートが確保できます。次に、$data1部分の工夫によって、継続する登攀ルートを確保して、XSS脆弱性を発動させます。なお、過激なエスケープ方式の場合には、$data1部分のルート確保は絶望的に困難です。また、金床さんによる8項目にわたる本来のJavaScriptエスケープ方法には、私のアタックルートは確保できません。これは、$data3の部分に対するルート確保が(私には)できないからです。ルート確保が可能な悪意あるアタッカーが、あるいは、存在しているいるかもしれませんが、そんなことは希望していません。世の平和のために、そんなアタッカーはいないものと期待したいです。善意のアタッカーさんには、是非、私の論考に欠けている部分、新しい$data3でのアタックル−ト、もしくは、本稿で触れていない、全く異なるヘリコプターによるアタック方法などをご教示頂きたく存じます。

$data3部分の登攀ルート

$data1;$data2;$data3
を、以下につっこむわけです。
    var strA = "$data";
ですから、
    var strA = "$data1;$data2;$data3";
となります。強調部分に留意願います。この強調部分には、単独の「"」が存在していますから、実行エラーの要因になります。単独の「"」の相方の「"」を$data3に埋め込もうとしても、 「"」が「\"」にエスケープされますからメタキャラクターの権限を失ってしまいます。
であるなならば、単独の「"」など相手にしない方針がよさそうです。
最初に思いつくことは、$data3として、「//」を与えることです。(「/」もエスケープされますが取りあえず気にしないでください)

    var strA = "$data1;$data2;//";

これで、3つに分割された最後のステートメントは消失します。コメントに成り果てるわけですね。

この方針は一般論では有効ですが、今回の課題Aにおいては「/」がエスケープされていますから、類似例を探してみましょう。

「//」の代わりに、「<!--」はどうでしょうか。これでも分割された3個の断片の最後のステートメント部分がコメントになってしまいます。

※ここが、徳丸さんによるエスケープ方法の$data3方面におけるルートの弱点であると当初本稿で主張したかったのですが、その後、なんのことはない、1番から8番まで全部そろっていても駄目なケースがあるのだと、編集途中で気がつきました。お恥ずかしい限りです・・・

$data3部分の登攀ルート(Opera9にて)

手元のOpera9(build 8585)においては、$data3 として、
「var [0x81]」が有効のようです。ただし、charsetがShift_JISの場合でのみ検証しました。結果として、分割後の3番目のステートメントとして

var [0x81][0x22];
が、エラーなしとなります。解釈として[0x81][0x22]というstringなりobjectなりを宣言できたというだけのようです。
$data3部分のアタックに対しては、本稿で話題にしている8項目にわたるエスケープでは対処できないことになります。
毎日すこしずつ書いていますが。このことがわかりはじめたので、本稿も大幅に修正しながら書き進めていくことになりそうです。

$data3部分の登攀ルート(Opera9にてパート2)

charsetがEUC-JPでも同様なルートがあります。

var [0x8F][0x22];
が、エラーなしとなります。
実はこれ、ちょっと変なのです。EUC-JP的には。[0x8F]は、SS3という名前(シングルシフト)の制御符号なのですがそれに続く2バイトまでを解釈するはずです。この例ではバイト数がたりません。セミコロンまで食べているとは思えないのですが・・・
charsetがEUC-JPのルートの第二弾。
var [0x8F][0x8F][0x22];
これもまぁ、エラーなしで通るのですけれど、変といえば変です。最初の[0x8F]に連続している2バイトは、EUC-JPで認められていない不正な符号です。これはおかしいですね。
※SS3=[0x8F]に続くコードは、補助漢字としてまっとうな符号であるべきです。「var = ●;」(●は補助漢字)がJavaScriptとして成立しうるのであるならば、●はきちんとコード表に割り当てられているできでしょう。

なお、[0x8F]を3個並べた場合には、Operaはさすがにエラーを返しました。文字列が閉じていない旨のエラー。[0x8F]群が「"」を食べてくれないからです。それはそうですね、[0x8F]は継続する2バイトまでにしか影響を与えないのですから。
・・・Operaばかりいぢめているわけではありません。IEを調べてみます。

$data3部分の登攀ルート(IE6にて)

Shift_JISで「"」を食べてしまう漢字を構成することを断念しましたので(直感的にも無理そうですし)、EUC-JPでアタックを試みました。上記のOperaの例にならって[0x8F]を1個2個とならべてみたもののうまくいきません。ところが、3個ならべたところで・・・

var [0x8F][0x8F][0x8F][0x22];
これがエラーなしに通ってしまいました。[0x22]が食われてしまいます。これは大変におかしな挙動です。一番左の[0x8F]は、3バイト後の[0x22]まで手が届かないはずです!理由を考えてみましたが、皆目見当がつきません。どうやらOSの癖に引きづられて解釈の途中にShift_JISないしそのMS版の拡張であるCP932を間違って使っているような気がします。
なお、[0x8F][0x8F][0x8F]は、前にも述べましたが、EUC-JPとして不正です。

$data3部分の登攀ルート(Firefoxにて)

EUC-JP方面でのアタックはおそらく不可能である事と個人的に感じています。素晴らしいですね。補助漢字にもしっかり対応していますし、正当な漢字以外では、JavaScriptの変数名として扱わないようですし。皆さんの常識にもあるようにShift_JIS方面でのアタックは最初から不可能と感じて調べませんでした。
EUC-JPにおいて、以下を作成してFIrefoxIEとで、それぞれソース表示をしてみますと、EUC-JPでの補助漢字の取り扱いに差が出ていることがよくわかります。

var [0x8F][0xBC][0xF4];

[0x8FBCF4]は、SMAPの草彅君の「彅」の字に相当します。Firefoxでは完璧にこなしていますがIEではボロボロです。

$data3部分の登攀ルート(まとめ)

var ●[0x22];
の●の部分に何かを設定することで、[0x22]を吸収してまえば、エラーストップを回避でき、アタックルート確保が可能でした。
また、次のように「//」や、「<!--」などで、コメント化することも有効でした。

var コメント化の方法[0x22];
ロケールやcharsetから、もしくはブラウザの実装などなどからの影響が多大です。全部を調べつくすことはなかなか難しいですね。防衛の観点から見れば、ヘタをすれば危険な状態があるということになります。

$data1部分の登攀ルート

$data1部分について考えます。
ここではEUC-JPを例にとります。
$data1に対して、アタッッカーが、

[0xC0][0x22]
を与えると、エスケープされて、
[0xC0][0x5C][0x22]
となります。なので、3つに分割されたステートメントの最初の部分は、
    var strA = "[0xC0][0x5C][0x22];
となります。この時点で、IE,Firefox,Operaなどは、[0xC0][0x5C]を、EUC-JPについてのマルチバイトな文字として取り扱いますから、これを●とすれば、
    var strA = "●";
となります。結局、せっかくの「"」→「\"」のエスケープが無効化されるわけです。
※お気づきのようにShift_JISに関しても同様な手法はありえます。

$data2部分

アタッカーは、悪意あるコードをここに書き下すことでしょう。以上でXSS攻撃が成立してしまいます。

登攀ルートの一例のまとめ

本稿の課題Aにおいて、5番のエスケープ(「<」の始末)を省略した場合には、IE6とOpera9.02とFirefox1.5.0.8においてXSS攻撃をこうむることがあります。
また、1番から8番までフルにエスケープしてもなお、IEOperaとで、XSS攻撃をこうむる場合があります。
EUC-JPとShift_JISのケースだけを論じていますが、他のcharsetにおいても安全性を断言できないことと思われます。

対策の結論は・・・

そもそも、HTML上のscript要素に書かれた「strA="ここにサーバから任意のなにかがはいっちゃう";」という形式は、バイナリセーフではありません。従って中途半端なエスケープでは効果が保障できないことがわかります。
すでに記述したXSS対策1もしくはXSS対策2が安心です。設計要件のせいで、どうしても適切なブラックリストを通過した後にエスケープせざるをえないのならば、XSS対策3がよいでしょう。なにがなんでも全部エスケープするしかないのです。そして、XSS対策3だけでは、うっかりした記述を継続するステップでしてしまえば、たちどころにXSS攻撃を食う可能性があるのだと覚悟しておくべきです。

最後に

金床さん、寺田さんによる各種文献が非常に勉強になりました。ありがとうございます。寺田さんのサイトは現在閉鎖されているようですが、復活を是非期待しております。佐名木さん、面白い資料の紹介をありがとうございます。
徳丸さん、ひきあいにだしてしまってごめんなさい。