|
|
||
twitterにXSSがあったので報告した。見つけた/報告した日は16日で、直った日は18日。
(大きな騒ぎにならなかったので)具体的にどこのサービスで問題が起きたという話は把握してない。しかし少なくとも、バグが存在していた期間中にtwitter widgetsを貼り付けられるサイト上でXSSが可能だったのは把握していて、具体的に危険な挙動になりそうなのは(本来自由にscriptが書けずブログ表示ドメインと管理ドメインが同一である) はてなやgooブログだ。簡単に言えばtwitter.comと同様に「twitterの出力するJSONを信用している」と、twitterのバグに引きづられて、他のサイトにも脆弱性が生まれることになるので、そういう設計は避けなければならない。
長くなりそうなので続きはあとで書く → 書いた
この記事は若干古いので2010年現在のステータスを調べるとする。こういうコードで検証できる。evalだとFirefoxで異常に遅くなったのでFunctionにした。
for(i=0;i<65536;i++) {try { new Function("a", 'var s="' +String.fromCharCode(i)+ '"' ) }catch(e){ console.log(i) } }
結果はこうなった
| Firefox3.6 | 10,13,34,92,8232,8233 |
| Chrome8 | 10,13,34,92,8232,8233 |
| Safari5 | 10,13,34,92,8232,8233 |
| Opera10.63 | 10,13,34,8232,8233 |
| IE9(standard) | 0,10,13,34,92,8232,8233 |
| IE9(quirks) | 0,10,13,34,92 |
という状況だった。
出力されるJSONをJSON.parse以外の手段でパースする場合、つまり
には、JavaScriptのソースとして評価されるので、ECMAScriptで文字列リテラル内に使えない文字 + 処理系依存で幾つかの文字が使えなくなるという状況になるわけだ。\u0020以下の制御文字はエスケープせよとJSONの仕様に含まれているので、古いバージョンのブラウザを除けば問題を起こすのは \u2028, \u2029 である。
で、(やや憶測も含むが)twitterは何らかの理由で出力されたJSON内に含まれる \u2028 \u2029 を置換している。自分はこのような挙動を示すJSONライブラリを知らないし、おそらく生成されたJSON文字列を正規表現で置換する処理が挟まっている。ややこしいのはU+2028のコードポイントを持つ文字ではなく、JavaScriptの数値参照で書かれた状態の "\u2028" を置換している。JSON内では数値参照になっているので、本来置換する必要がないにもかかわらず置換している。
JSONの中に\u2028, \u2029が含まれていて問題が起こるのは、
もし行うとしても、それはJSONに変換する前に行うべきだと思う。\u2028 を使わずに、直接埋め込んだ場合でなければJavaScript上でエラーは発生しないので、通常置換する必要はない。で、もしユーザー入力にこれらの文字が含まれていて特に対策されていなかったとしても「変な文字いれたらJavaScriptがエラーになる」というだけで、通常脆弱性にはなることはない。
間抜けだと思うが、多分こういうことだと思う。単なる推測なので別の要因があるのかも知れない。これは自分の知る限りtwitter独自の問題で、もし特定のライブラリやフレームワークやミドルウェアで、このような挙動を示すものがあるなら教えてほしい。
json = "\\u2028 \\\\u2028 \\\\\\u2028" # => \u2028 \\u2028 \\\u2028 puts json # 多分こうなっていた NG_UNICODE_CHARS = [2028, 2029] re = /\\u(#{ NG_UNICODE_CHARS.join("|") })/ # => /\\u(2028|2029)/ puts json.gsub(re, "") # 本来想定しているコード # \uの前に奇数個 \ が付いていたら何もしない。 re2 = /(\\*)(\\u(#{ NG_UNICODE_CHARS.join("|") }))/ puts json.gsub(re2) {|s| $1.length % 2 == 1 ? s : $1 }
間違ったコードの出力結果は
"\u2028" → "" "\\u2028" → "\" "\\\u2028" → "\\"
こうなる。U+2028のコードポイントを持つ文字は空になり、\u2028はJSONにencodeされる際に \ → \\ となるので \\u2028 になる。それが \u2028 を置換する処理で \ のみ残ってしまう。
twitterの過去のXSSの多くはtwitter.com上での文字列処理(エスケープ漏れ、自動リンクなど)に起因していたが、今回報告したXSSはJSONの出力結果にタグが含まれるという問題なので、JSONを利用していてtweetにタグが含まれない前提で設計している全てのサイトに影響が及ぶことになる。
これは、相手サービス(あるいは提携関係にあるサービス)のAPIの出力するレスポンスをどこまで信頼できるのか、という問題でもある。例えば、認証情報を扱わないでセキュリティに対して気を使わなくても良いサイト上でJSONPを使ったマッシュアップを行うようなケースで(HTMLエスケープ済みであるということが仕様に明文化されているなら)一度デコードして再度エンコードする、という処理は無駄であるかもしれない、Twitterはある程度信頼できて、TwitterがAPIを修正すれば自動で問題は解決するからだ。しかし、一定以上のセキュリティレベルが求められるサイト(個人情報を扱ったり認証cookieを持っている)であれば、(それがHTMLエスケープ済みであると明文化されていたとしても)相手サービスのAPIの仕様変更や、バグがある可能性に備えて、自分たちの責任で出力する段階で安全になるようにエスケープ(エンコード)してやらねばならないだろう。
あとで書く