アメブロでの禁止タグチェックの誤検出とjavascriptスキーム

これは元々2009/10/24にAmebloに投稿した記事です。

禁止タグ

前回の記事をamebloにアップしようとしたところ、
何故か禁止タグが存在するというエラーメッセージが表示され投稿出来ません。

以前書いたように、この記事はまずローカル環境のファイルに対する執筆/編集を行い、
最後にamebloの標準エディタHTMLタグ表示フォームにコピペして投稿をしていますが、
当然禁止タグの存在は判っているので、それらを使っている訳は有りません。
が、万が一という事も有るのでもう一度確認してみましたが、やはり禁止タグは存在していません。

埒があかないので、どの部分に問題が有るのか確認する必要が有りますが、
禁止タグ云々のエラー表示は、IE6のJavaScriptエラーメッセージ並に不親切なので、
どの部分の文字列なのかは表示されていません。
# そもそもameblo上で長文を書くような物好きな人間の存在を考慮していないのではないかと。

そんな場合には、古典的な手法である文章の断片毎に確認してエラー候補を絞り込むという事を行いますが、
その単純作業により最後に残ったのが下記のようなリンク文字列、
<a href="http://jp2.php.net/manual/ja/class.domdocument.php" target="blgr">DOMDocument</a> の中の、class.domdocument.phpでした。


http://document.example.com

どうやらdocument.phpやらdocument.example.comなど、document.の部分が誤反応する模様です。
要するにdocument.writeやdocument.cookieなどの文字列への対応を想定していると思われます。

リンクURIには、<a href="javascript:alert('foobar')">foobar</a>とJavaScriptなども書けてしまうので、
hrefで指定される文字列をチェックするのは当然ですから、
これは単にそのチェック処理の副作用(誤検出)だと受け取り、
これ以上特に深く追求する気も起きませんので、とりあえず対応を考えます。


本文中に記述されたユーザー定義のJavaScript

とはいえ特に考える事も無く、dを&#100;に換え試してみるとあっさり通りますので、
これにて解決とする事とします・・・
と言いたい所ですが、これがチェックをすり抜けるという事は、
XSS脆弱性にも繋がる恐れのあるJavaScriptの実行が可能になる事も十分疑われますので、
その点を確認してみますと、残念ながら懸念通りの展開の様子です。
# そもそも自分自身で管理するブログの記事でXSSと言えるか、という話にもなりますが。


文字参照 - Character Reference

HTMLやXMLなどでは、タグの無効化として<>をそれぞれ&lt;&gt;に変換する処理や、
"(ダブルクォート)の&quot;や'(シングルクオート)の&#039;への変換などが行われますが、
この&英文字;や&#数値;のような形式の文字列と、実際に表示される文字や記号の対応を文字参照と呼びます。
# と言ってもcharacter referenceの日本語訳が"文字参照"になっているらしいというのは今回初めて知りましたが。

この文字参照では、上記のようなHTML中で意味を持つ記号を無効化するような変換や、
©(&copy;)などの特殊記号の表示に使われる事は広く知られていますが、
それ以外にもBlog(&#x42;&#x6c;&#x6f;&#x67;)や、ブロガラ(&#x30d6;&#x30ed;&#x30ac;&#x30e9;)のように、
通常の文字を表現する事も出来ます。


URI文字列内での文字参照

今回は、その文字参照への対応が不十分だった事による問題となります。
より正確に言うならば、アンカータグ<a>href属性や<img>などのsrc属性により指定されるURI
ブラウザ側での扱いの特性を考慮していなかった事が原因と思われます。

意味のある記号を表示上の形体としての記号にするのが文字参照とも言え、
代表的な<>の&lt;&gt;化のように、HTML中では単なる文字として扱われ、
また、<script>を<scrip&#x74;>とタグ名を文字参照で記述した場合も無効なタグとして扱われるだけとなります。
が、アンカータグのhref属性などのURI指定に使われる場合には違ってきます。

例えばHTML中で意味を持つ特殊記号である&を記述したい場合には、&では無く&amp;と記述する必要が有り、
それはURI指定でも例外ではありません
一方、Webサーバー/サーバー側スクリプトでは、
queryでの個々の値の組み合わせの区切り文字に&を利用する事が事実上の標準となっており、
# W3Cでは"&"の替わりに";"を使うべきだと推奨していますが。
Webブラウザは言わばその間を取り持つように、
HTML中のURI指定では&amp;と記述された文字参照を本来の&1文字に戻してから、
サーバー側へリクエストを送ります。


javascriptスキーム

これがhttpスキームが指定されるURIならそれほど問題無いのですが、
2009年現在の多くのPCブラウザでは、
アンカータグのhref属性で指定されるURIとしてjavascriptスキームも利用出来ます。

amebloでもURI中の任意の場所にあるdocument.が禁止タグ扱いされるくらいですから、
当然javascriptスキーム指定も禁止タグ扱いとなり投稿は出来ません。
が、例えばsを文字参照として記述したjava&#115;criptならチェックをすり抜けて投稿出来てしまい、
クライアント側のWebブラウザURIでの文字参照の扱いにより、
java&#115;criptがjavascriptスキームの指定と復元され、
そのjavascriptスキームが指定されたリンクをクリックする事でJavaScriptが実行されてしまいます。

さらにsrc属性のURI指定でもjavascriptスキームが有効になっているブラウザもありますので、
そのようなブラウザ上では、
ページを表示しただけでimgタグに記載されたURIJavaScriptが実行されてしまう恐れも有ります。
<img src="java&#115;cript:alert( 'Oops!' );" />

今回の記事のそもそもの発端である禁止タグチェックの誤検出への対処法にも書いたように、
document.も文字参照を使う事によりチェックを回避出来ますので、
document.writeやdocument.cookieなどへのアクセスも可能となってしまいます。


ユーザー定義CSS

アンカータグにjavascriptスキームを利用した悪意あるスクリプトが埋め込まれている状態では、
それをクリックしなければ良いだけですが、
amebloではかなり自由度のあるユーザー定義のCSSが利用出来ますので、
悪意あるアンカータグをClickjacking(UI redressing)的に他のエレメントの上に密かに設置する事や、
position:fixedでbackground:transparentな全画面に表示されるブロック要素として配置する事も可能になりますので、
クリックされてしまう危険性も高くなります。


とりあえずの結論

amebloの内部仕様については当然判りませんが、
今回のような問題への簡易な対応としては、
チェック処理を(数値)文字参照から本来の文字に戻した文字列に対して行うようにすれば、
それほど手間が掛からず修正可能なのではないかと思われます。

ちなみにBlogaraでは、数値文字参照だけを文字に戻す処理は下記のような実装となっています。

// PHP(文字コードUTF-8)で数値文字参照だけを本来の文字に戻す処理。
// より正確な処理を行うなら、10進数指定か16進数指定かで分ける必要有り。
// ブラウザによっては、文字列中に記述される文字参照の最後が;(セミコロン)で終らなくても、
// 改行やタグのように明らかな境界では無く、無効な文字参照指定文字が出現するまでを
// 文字参照として解釈するので注意。(&#100zを&#100;zと判断するなど)。
// # 一方PHPのhtml_entity_decodeやmb_convert_encodingでは、
// # セミコロンで終らない文字参照は文字に復元しないので注意。
preg_replace('/(&#x?[0-9a-f]+);?/uei', 'html_entity_decode("$1;",ENT_QUOTES,"UTF-8")', $str );

このように長々と書きましたが、
リンク文字列中の文字参照が本来の文字/記号に戻されてからURIとして評価されるという事は知ってはいましたが、
これがjavascript:のようなスキーム指定にも適用されJavaScriptが実行されてしまうという
XSSに繋がる危険性のあるものだとは自分も正直この時点まで認識していなかったので、
これからは十分注意すべきだと痛感した今回の一件でした。

また、現在のamebloでは、ブログ本文に記述されるユーザー定義のJavaScriptが許可されていないので、
この文字参照を使った抜け道は不具合なのではないかと考え、
運営に報告すると共に、修正対応が行われるまではこの記事の公開を行わない事にします。


追記1 (2009/11/05)

その後文字参照 XSSあたりでググってみると、
どうやら文字参照javascriptスキームの問題は既に何年も前から周知の脆弱性の原因の模様です。

しかし、今回の問題は修正が容易であると思われるにも拘わらず、
運営側からの返答も無く、一向に修正される気配も有りませんので、
IPAXSS脆弱性に繋がる任意のJavaScriptが実行される不具合が存在するという報告を行いました。

が、アクセス解析としてGoogleAnalyticsが使えるか否かを調べている過程で、
サイドバーのフリープラグインでは、文字参照を使わずともあっさり任意のJavaScriptが記述出来るというオチが判り、
ameblo的にも任意のJavaScript実行は容認状態である模様で何やら肩透かしを食らった感じですが、
とりあえずブログ本文中でのJavaScript記述という規定外の動作となるので、
運営/IPAどちらかからの経過報告を待つ事にします。


追記2 (2009/12/23)

11/26にameblo運営よりメールがあり、

との事ですが、確認してみましたが修正はまだ行われていません。


さらにその後12/22にIPAより、
ウェブサイト運営者側での修正が行われたという内容のメールを頂きましたので確認してみました。

java&#x73;cript:のように文字参照を使ったjavascriptスキーム指定は、
禁止タグチェックに引っ掛かり投稿出来ないように確かに修正されています。
が、
上記Blogaraでの数値文字参照の復元コードのコメントに書いた、
ブラウザによっては、文字列中に記述される文字参照の最後が;(セミコロン)で終らなくても、
改行やタグのように明らかな境界以外にも、無効な文字参照指定文字が出現するまでを
文字参照として解釈するので注意。(&#100zを&#100;zと判断するなど)。
のようなブラウザ側での特殊な動作が考慮されていないようで、
javascri&#x70t:と記述した場合には、amebloでの禁止タグチェックをすり抜けて投稿出来、
ブラウザ側でjavascriptスキーム指定に復元されJavaScriptが実行されてしまいます。

アメーバブログでサーバー側のプログラム言語に何を使っているのかは不明ですが、
PHP文字参照の復元に利用されるhtml_entity_decodeやmb_convert_encodingでは
ブラウザでの特殊動作とは異なり、
セミコロンで終らない文字参照は文字に復元しませんので、
そのような事も関連しているのではないかと思われます。

という訳で、依然不都合があるという事をIPAにメールで返信し、
記事の公開はまだ行わない事にします。
# ちなみにdocument.の禁止タグ誤検出も修正されていませんでした ;(


追記3 (2010/01/30)

前回よりさらに1ヶ月以上経過した01/29に、 再びIPAよりウェブサイト運営者側での修正が行われたという内容のメールを頂きました。

こちらで確認してみたところ、今回は文字参照関係の修正がキチンと行われているようです。
が、そもそもの発端であるURI中のdocument.の禁止タグ扱いの語検出は残ったままで、
document.を含むリンクが記述出来ないという妙な仕様のブログとなっています。


しかし、ソフトウェア関係の技術者のブログ執筆者が皆無なamebloとはいえ、
日本最大規模とも言えるブログサイトにおいて、このような問題が放置されていたという事実は、
それぞれのシステムにおけるURI文字参照の処理について見直してみる良い機会かもしれません。

と、ありがちな締めコメントを吐いてみましたが、
自分自身もdocument.の誤検出が無ければ全く気付かず、
特に調べてみようとも思っていなかったのは事実ですので、あまり偉そうな事も言えません。/grin


参考資料

W3C: Character references
W3C: Character entity references in HTML 4
W3C: Ampersands in URI attribute values
Wikipedia: 文字参照
JVN: 脆弱性情報対策データベース クロスサイトスクリプティング

distraid.co.jp: 半角全角変換ページの文字コード関連ツール - 特定の文字を数値文字参照で記述する時に利用
distraid.co.jp: Firefoxのプラグイン NoScriptの説明とXSSついて - 既に約2年前に書いたネタなので正直古い