iモードブラウザ2.0のJavaScriptではiframe内のコンテンツを読み出せない
iモードブラウザ2.0では、同一ドメインであっても、iframe内のコンテンツがJavaScriptにより読み出せないよう制限が掛かっていることを確認しましたので報告します。
【追記】元の内容には、重大な事実誤認がありました。正確には、同一ドメイン・同一ディレクトリであれば読み出せます。詳しくは追記2をご覧ください。
きっかけ
ケータイtwitter(twtr.jp)においてDNS Rebinding攻撃に対する脆弱性を発見・通報し、即座に修正された - 徳丸浩の日記(2010-02-22)にて既に紹介したように、twitter.comの日本のケータイ向けフロントエンドであるtwtr.jpにDNSリバインディング脆弱性があったことを確認・報告し、直ちに修正されました。このエントリの中に、以下のように書いています。
すぐに確認作業が終わるだろうと思っていたが、意外なところで失敗した。ログイン・リクエストのPOSTがうまくいかないのだ*1。早く確認を終わらせないと、もたもたしているうちに脆弱性を悪用されるとまずい。結局、XMLHttpRequestをあきらめ、IFRAME要素を用いることにした。
最初、IRAME要素をDOMで動的に作ったりしていたのだが、IFRAME内のFORMをうまくSUBMITできない。このため、以下のような構成にした。
このうち、XMLHttpRequestでPOSTリクエストがうまくいかない理由は、iモードブラウザ2.0のXMLHttpRequestでPOSTデータの扱いが困難になった - 徳丸浩の日記(2010-01-18)で報告したように、iモードブラウザ2.0ではsetRequestHeaderメソッドが殺されていたために、Content-Typeを設定できないことが原因でした。
もう一つの、「IFRAME内のFORMをうまくSUBMITできない」件について、しばらく放置していましたが、この度検証しましたので報告します。その理由は、表題のように、iframe内のコンテンツに対してJavaScriptからのアクセスが禁止されたことでした。
検証用スクリプト
以下のコンテンツを用いて検証しました。ご覧のように、iframe内にdiv要素(id=innerdiv)があり、そのinnerHTMLを取得・表示するものです。
p.html(外側)
<html>
<body>
<script>
function get() {
try {
var n;
n = 1; var iframe = document.getElementById("iframe01"); // HTMLIFrameElement
n = 2; var cw = iframe.contentWindow; // Window
n = 3; var cd = iframe.contentDocument; // HTMLDocument(使っていない)
n = 3; var doc = cw.document; // HTMLDocument
n = 4; var x = doc.getElementById("innerdiv").innerHTML;
n = 5; document.getElementById("output").innerHTML = x;
n = 6;
} catch (e) {
document.getElementById('output').innerHTML = n + ':' + e.message;
}
document.getElementById('debug').innerHTML = 'cw=' + cw + '<br>cd=' + cd;
}
</script>
<input type="button" value="GET" onclick="get();"><br>
<iframe width=300 height=50 id="iframe01" src="pch.html"></iframe>
<div id="output"></div>
<div id="debug"></div>
</body>
</html>
pch.html(iframeの内側)
Chromeでの実行結果(Firefox、Opera、Safariも同様)。
<html>
<body>
<div id="innerdiv">this is a pen.</div>
</body>
</html>
IE7での実行結果
Softbank 932SHでの実行結果
iモードブラウザ2.0(P-07A)での実行結果
ご覧のように、iモードブラウザ2.0のみiframe内のコンテンツを読み出せず、iframeのcontentWindowおよびcontentDocumentはundefinedに設定されています。
今度はiframe内コンテンツの別の読み出し方として、高木浩光氏の日記高木浩光@自宅の日記 - 共用SSLサーバの危険性が理解されていないで紹介されている方法を少し改変して試してみました。
<body>
<iframe src="/example.com/" name="foo"></iframe><BR>
<input type=button value="Get Cookie" onclick="getcookie();">
<script>
function getcookie() {
try {
var n = 0;
n = 1; var doc = foo.document;
n = 2; var x = doc.cookie;
n = 3; document.getElementById("out").innerHTML = x;
} catch(e) {
document.getElementById('out').innerHTML = n + ':' + e.message;
}
}
</script>
<div id="out"></div>
</body>
先ほどとは違い、「operation is inhibited」という例外が発生しています。
iframe内要素を読み込めなくした理由は何か
iモードブラウザ2.0でiframe内コンテンツを参照できないように制限が掛けられていることがわかりました。しかし、他のブラウザには、そのような制限はないので、なぜ制限を掛けたのか、その理由が気になります。
一つ思い当たるのは、既にP-07AでもJavaScriptが再開され、あらたな制限がみつかった - ockeghem(徳丸浩)の日記で報告しているように、XMLHttpRequestで親ディレクトリのコンテンツを参照が禁止されたこととの関連です。以下のような仮説はどうでしょうか。
一例として、二つのコンテンツが同一ドメイン上の別ディレクトリに配置されているとします。
- http://example.jp/~foo/
- http://example.jp/~bar/
上記に示したような、同一ドメイン上の別ディレクトリに配置された別のコンテンツについて、相互にJavaScript等でアクセスができないように制限したという仮説です。「XMLHttpRequestで親ディレクトリのコンテンツを参照が禁止された」件は、まさにこれに該当します。
一方、iframeの方は一見関連がないように見えますが、先に参照した高木浩光@自宅の日記 - 共用SSLサーバの危険性が理解されていないで説明されているように、異なるディレクトリに配置されたコンテンツのCookie値を参照する方法として、iframeを使う手法があります。これを禁止したのではないかという仮説です。
本当のところは分かりませんが、上記の仮説でつじつまが合うような気がします。しかし、PC向けブラウザではアクセスできるものが、iモードブラウザ2.0ではアクセスできことは、既存のJavaScript資産やプログラマの知識が有効活用できないことになり、かつNTTドコモの公式ドキュメントにもこのような記述は見あたらないことから、もっと情報をオープンしていただきたいものです。
また、ソフトバンクに関して言えば、私が検証した932SHは、正式にJavaScriptが搭載された機種ではないことから、正式にJavaScriptが搭載された944SH/945SHではどうなるかが気になります。検証環境を公開しますので、944SHあるいは945SHをお持ちの方は試して頂けないでしょうか。
http://www.tokumaru.org/p.html あるいはQRコードから検証コンテンツを閲覧ください。
追記(2010/08/04 18:00)
id:harupuさんからブクマコメントを頂戴しました。
普通にname属性使ってiframe_name.document.documentElement.outerHTMLとかで読めると思ったけど……。PC向けブラウザでもJavaScript自体まちまちな実装だし多少違いがあるのは仕方ないのでは。
私も最初は実装の違いかと思っていたのですが、iframe_name.documentを参照すると、「operation is inhibited」という例外が発生します(2番目のスクリプトの結果参照)ので、意図的に禁止されているものと推測します。また、ソフトバンクとドコモは共にACCESS社のNetFrontブラウザを採用していることと、P-07Aより古い932SHがiframe内コンテンツにアクセスできて、新しいP-07Aはiframe内コンテンツにアクセスできないことからも、意図的な制限であることの傍証になると思います。
追記2(2010/08/04 20:15)
id:harupuさんから再度のコメントをいただきました。
確認したところ、確かにそうでした。親コンテンツと同一ディレクトリもしくは、親コンテンツのサブディレクトリのコンテンツであれば、
iframe_name.document.documentElement.outerHTMLなどで読み出せます(実際にはinnerHTMLで確認)。お詫びして訂正いたします。
すなわち、iframe自体はどのドメイン、ディレクトリであっても作れますが、JavaScriptを使ってiframe内のデータにアクセスするためには、同一ドメインかつ、同一ディレクトリまたはサブディレクトリのコンテンツに限るという条件がつきます。これは、XMLHttpRequestでデータを読み出せる条件と一致します。
このため、実験の結論は間違ってしまいましたが、そうする理由の予測はかえって裏付けられたような気がします。ドコモは、何が何でも別ディレクトリのコンテンツをJavaScriptから読み出させたくないように思えます。だが、なぜ?