Firefox 2.0.0.20 におけるinnerHTMLの不備

次のようなコードをウェブアプリケーションで取り扱い、onclick属性で実現したいとします。

alert("ユーザが出力する文字")
次のような条件を考えて見ましょう。ユーザは、"を与えました。ですので、以下を考えたいわけです。

alert(""")
ここで、以下を参考に、JavaScript文字列内に、通説によるJavaScriptとしてエスケープすべき文字がみあたらないことに留意してください。

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

そこで、HTML4.01で普通に書くならば、XSS対策として、HTMLレベルでの文字参照でのエスケープをほどこしてやることにより、ウェブアプリケーションは以下の断片を出力することが考えられます。JavaScriptレベルでのエスケープは不要と考えられるからです。

onclick="alert(&quot;&amp;quot;&quot;)"
この断片をimg要素の属性として付加したものをイメージAとします。イメージAを上位ノードのdiv要素のinnerHTMLとしてコピーし、HTML内側の他のdiv要素のinnerHTMLで貼り付けることを考えてみます。貼り付けられたものをイメージBと呼ぶこととします。サンプルHTMLを以下に掲げました。


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">

<html lang="JA">
<head>
<meta http-equiv="Content-Script-Type" content="text/javascript">
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<style type="text/css" media="screen">
<!--
body { background-color: #000; font: 16px Helvetica, Arial; color: #fff; }
-->
</style>

<title>Sandbox</title>
</head>

<body>
<div id="i01">
<img src="http://mozilla.jp/img/tignish/video/play.png"
alt="dummy image"
onclick="alert(&quot;&amp;quot;&quot;)">
</div>
<div id="i02">
dummy string
</div>

<script type="text/javascript">
<!--
var q1 = document.getElementById("i01");
var q2 = document.getElementById("i02");

q2.innerHTML = q1.innerHTML; //これが問題のイメージのinnerHTMLによるコピーです。

alert(q1.childNodes[1].attributes.getNamedItem('onclick').nodeValue); //テスト1
alert(q2.childNodes[1].attributes.getNamedItem('onclick').nodeValue); //テスト2
// -->
</script>
</body>
</html>

上のサンプルで、テスト1、テスト2の場所に、コピー元とコピー先のonclickアトリビュートの内容をalertしています。Firefox 2.0.0.20 で上のHTMLを開きますと、テスト1のalert結果は以下のようになります。

alert("&quot;")
これは期待通りです。ですけれど、テスト2のalert結果は次のようになります。
alert(""")
これは期待したものではありません。実行するとJavaScriptレベルでエラーになってしまいます。実際、上のサンプルHTMLでは、コピー元とコピー先の2個の画像が表示されますが、おのおの、マウスでクリックしてやると、イメージAではきちんと実行されますが、イメージBでは、エラーになってしまいます。
以上は、単にエラーになった、ということを意味していません。上記の性質を使用することによって、ブラウザサイドでのXSSを発動させることが可能なケースが出てくるからです。JavaScriptの文字列の中で適切な文字参照を使うことにより、innerHTMLでのコピー先でJavaScriptインジェクションが可能になってしまいます。たとえば、上の例で考えた&quot;の代わりに以下のようなものが考えられます。
&quot;);alert(document.URL);//

なお、Firefox 2 の最後のリリース(2008/12/18)のFirefox 2.0.0.20 までには上の弱点がありますが、Firefox 3 シリーズ以後には見受けられないようです。以前ご紹介したOpera 9.27 (8841) におけるinnerHTML取得時の引用符解釈の不備と同様に、頭の片隅にいれておくことが必要だと思われます。

※2009/2/27に[これはひどい]IEの引用符の解釈というhasegawayosukeさんによる記事が出ましたが、もう少し早いタイミングであれば、OperaFirefoxでの類似バグが存在していたこととなります。ちなみに、Operaの方が先に修正されていたのですね。(;-p)