IEのcreateElementの不思議な挙動(その2)

IEのcreateElementに不具合がある

前回はdocument.innerHTMLを使っていましたので話の正体がみえにくいと個人的に感じました。そこでappendChildバージョンを作成してみました。

一部のバージョンのIEではdocumentObject.createElement( tagName )の引数:tagNameとしてDOMString を引渡した際に検査が充分でないため、指定された名前に不正な文字が含まれている場合でもINVALID_CHARACTER_ERRが発生しない…という理解でいいでしょうか…?

サンプル

<!-- saved from url=(0014)about:internet -->
<head>
<script>
window.onload = function() {
var invalidDATA;
invalidDATA = '怪しいデータがここに';

document.getElementById("H01").appendChild(document.createElement(invalidDATA));
}
</script>
</head>
<body>
<div id="H01">

</div>
</body>

きわめてシンプルです。invalidDATAに普通は想定しないデータを与えてみます。

属性を注入する

要素を作成するはずのcreateElementですが、不具合があるために要素に付随して意図しない属性の注入が可能かもしれません。そこでサーバ側でユニコードエスケープ済みもしくはJavaScriptエスケープ済みの文字リテラルが上記サンプルのinvalidDATAの『怪しいデータがここに』のところで代入するようになっているものとして考えてみます。『invalidDATA = '怪しいデータがここに';』なので取り合えずサーバ側でエスケープぐらいはしているだろうということです。
ユニコードエスケープが何かについては、JavaScript のリテラルに任意の文字列を出力してみる::bakera.jpを参照して下さいませ。また、JavaScriptエスケープについては、当日記のJavaScriptエスケープについて論考にも書いてありますのでよろしければどうぞ。
今回は下記の二通りの属性注入方法を考えます。なお、2番目のものはalert()の無限ループになるかもしれませんので試す際にはご留意下さい。

  1. onmouseover="alert(99)"
  2. style="width:expression(&#97;&#108;&#101;&#114;&#116;&#40;&#57;&#57;&#41;)"

onmouseover属性を注入してみる

ユニコードエスケープの場合
invalidDATA = '\u003C\u0074\u0065\u0078\u0074\u0061\u0072\u0065\u0061\u002F\u006F\u006E\u006D\u006F\u0075\u0073\u0065\u006F\u0076\u0065\u0072\u003D\u0022\u0061\u006C\u0065\u0072\u0074\u0028\u0039\u0039\u0029\u0022\u003E';
JavaScriptエスケープの場合
invalidDATA = '\x3Ctextarea\/onmouseover=\"alert(99)\"\x3E';

style属性を注入してみる。

ユニコードエスケープの場合
invalidDATA = '\u003C\u0074\u0065\u0078\u0074\u0061\u0072\u0065\u0061\u002F\u0073\u0074\u0079\u006C\u0065\u003D\u0022\u0077\u0069\u0064\u0074\u0068\u003A\u0065\u0078\u0070\u0072\u0065\u0073\u0073\u0069\u006F\u006E\u0028\u0026\u0023\u0039\u0037\u003B\u0026\u0023\u0031\u0030\u0038\u003B\u0026\u0023\u0031\u0030\u0031\u003B\u0026\u0023\u0031\u0031\u0034\u003B\u0026\u0023\u0031\u0031\u0036\u003B\u0026\u0023\u0034\u0030\u003B\u0026\u0023\u0035\u0037\u003B\u0026\u0023\u0035\u0037\u003B\u0026\u0023\u0034\u0031\u003B\u0029\u0022\u003E';
JavaScriptエスケープの場合
invalidDATA = '\x3Ctextarea\/style=\"width:expression(&#97;&#108;&#101;&#114;&#116;&#40;&#57;&#57;&#41;)\"\x3e';

まとめ

createElement( tagName )にtagNameとしてDOMString を引渡す時には、JavaScriptの文字列リテラルへの代入時にほどこすために使われているエスケープ済みだからというだけでは安心できません。一部のIEのために不本意なことかもしれませんが、必ず適正な要素として安心かどうかホワイトリストで検査しておくべきです。というよりも、ユーザが設定可能な要素をブラウザ側スクリプトで作り出すような処理はそもそも必要なのでしょうか?要検討ですね。

document.innerHTMLやdocument.write()の場合、HTMLに出力する直前にHTMLで使われる文字参照でのエスケープ(&gt;などにエスケープ)が有効です。このことから類推して、createElement( tagName )をした瞬間に事実上すでにHTMLに出力しているのだ、という見地から、文字参照でのエスケープを行うべき…と考えるべきでしょうか?なにしろappendChild時点ではエスケープをしたくとも出来ません、だからこそcreateElementの時点で…などどそんなことを考えることは、はなはだナンセンスですね。(;-

ネタ元

ネタ元として参照した記事は、今は散逸しているようですが、channel9.msdn.comドメインのchannel9.wikiでした。IE7が誕生するかしないかの頃のお話しです。たぶん、Microsoftさんはご存知だと思いました。

http://channel9.msdn.com/Wiki/InternetExplorerProgrammingBugs/

あたりで『Dynamically creating and DOM-inserting Radio Buttons 』というキーワードで検索するといいかもです。残骸が残っています。

余談

ブラウザサイドでJavaScriptを使ってのDOM派的XSS防衛というのはやはりまだまだ厳しいのでしょうか。サーバサイドのDOM派的防衛術は最強だと思っています。
…手元のFirefoxで"<p>"とかをcreateElementに突っ込んだらエラーを返してくれないのでびっくりしました。おまえもか????あれ?