2016.09.21
プロフェッショナルサービス事業部 寺田 健
(English version is available here).
URLのハンドリング処理のバグは、しばしばセキュリティ上の問題の原因となってきました。筆者が昨年報告したSafariのXSSもその一つの例です。本日は、この脆弱性の詳細を書きます。
概要: 細工したリダイレクトURLにより下記が可能。
① Host header manipulation
② Origin Confusion XSS
いずれも、一定の条件を満たすWebアプリにおいて、情報窃取/改竄の被害につながる。
影響を受けるソフトウェア: Safari v9.1.2未満、iOS v9.3.3未満、tvOS v9.2.2未満
推奨事項: 2016年7月18日以降にリリースされたバージョンへのアップグレード。
Safariの修正: リダイレクトURLの検証強化。不正な場合はエラー画面を表示する。
識別子: CVE-2016-4585
下図のように細工したLocation応答ヘッダ(302 or 307ステータス)をSafariに返すと再現します。
URLのポート番号に数字以外が入っています。それを受けたSafariはexample.jp:80に接続し次の要求メッセージを送ります。
Hostヘッダに不正なポートが出力されています。これが意味するのは「他のユーザがどこかのサイトに接続する際のHostヘッダを操作できる」ということです。ただし、URLに出現してはならない文字はURLエンコードされる、操作できるのは「:」より後ろのみ、という制限付きです。
これがどのような影響をもたらすかみていきます。
(テストをする場合は、Burp等のProxyを外した上で、古いSafariで実行する必要があります)
上のように使える記号は「'」や「&」などに限られています。
最初に筆者が試したのは「'」で括られたvalue属性値に出力される単純なパターンでのXSSです。
意外にもHostヘッダの反射でもXSSフィルタが動作しました。もちろん下記のようにコンテキスト次第ではまだ攻撃の可能性はあります。
しかしこれではひねりがありません。もう少し掘り下げていきましょう。
Hostヘッダは下記のような場所にも出力されます。
<script src="http://(HOST)/js/jquery.js"> <link href="http://(HOST)/css/style.css" rel="stylesheet" type="text/css">
そういうプログラムの例は、githubのコード検索(要ログイン)で見つけることができますし、私自身も診断時に実際に見てきました。それほど珍しくはないです。
ここでは「<script src=」のシナリオについて考えてみます。まずはHostヘッダを「example.jp」から「example.jp:xyz」に操作したとしましょう。
<script src="http://example.jp:xyz/js/jquery.js">
この場合、SafariはURLを不正とみなしてこのJSをロードしません(このようなURLはLocationでのみ解釈されます)。また、仮に「example.jp:80」からロードされたとしても、正しいサーバからJSが読み込まれるだけなので意味がありません。ここでの攻撃者の目標は、外部のサーバからJSを読み込ませることです。
筆者が試行錯誤の後に辿りついたのは下記のような攻撃方法です。
攻撃者サーバからの応答:
攻撃対象サーバへの要求:
上のLocationを受けて、Safariはexample.jp:80に接続し、下記のHostヘッダを送ります。
先頭の「a@」(Basic認証のクレデンシャル部分)は除かれます。
攻撃対象サーバの応答:
対象アプリはHostヘッダの値を「<script src=」のホスト名に出力します。
ここでまた「@」以前の部分が取り除かれます。結果としてこのJSはevilホストから取得され、XSS攻撃が成功するということになります。
ちなみに、この攻撃ではXSSフィルタは反応しません。また、Basic認証の「@」付きURLへのアクセス時に表示されるフィッシング警告画面も表示されません。つまりこれらはこの攻撃の妨げにはなりません。
上の攻撃は情報窃取にも転用できます。(正常な処理として)機密情報を含むURLにリダイレクトするWebアプリがあるとします。
Location: http://(HOST)/foo?token=fj0t9wj958...
この場合、攻撃者は前述の「@」を使った手法で被害者のHostヘッダを操作することにより、「token」を盗むことができます。
筆者がこのシナリオを割と現実的だと思うのは、Hostヘッダの出力先として筆者が最も多く目にするのがLocationヘッダだからです。これは、Webアプリの開発者がそうしているというよりも、一部のプラットフォームのリダイレクト機能が、Hostヘッダを頼りにして、アプリから渡された相対URLを絶対URLに書き換えてしまうからだと思われます。JavaのServletHttpResponse#sendReirect()などがその例です。
ちなみに、この一部のプラットフォームの挙動の背景にあるのは、Locationへの相対URLの使用を認めていなかったRFC2616(前のHTTP/1.1標準)です(新しいRFC7231は認めています)。
Location以外にも、「<form action=」「<a href=」などのHTMLのURI属性も、Hostヘッダがホスト名として使用されるならば、情報を窃取する攻撃の対象になりえます。
上記の検証をたまたま弊社のサイト「http://www.mbsd.jp/」で行った際に、ちょっと引っかかったことがあります。
下記は通常の画面です。
しかし「http://www.mbsd.jp:xyz/」にリダイレクトすると、下のような寂しい画面となります。
このような画面になるのは、相対URLのリソース(画像を含む)が正常に読み込まれていないためです。
ブラウザで何が起こっているか調べてみます。
まず分かるのは文書のオリジンが壊れているということです。相対URLのリソースが読み込まれなかったのはおそらくそのせいです。さらにCookieへのアクセスもSecurityErrorで拒否されています。下記のように、要求メッセージの方には正常なCookieが含まれているにもかかわらずです。
壊れたオリジンのせいでCookieにアクセスできないという制約は、攻撃者が挿入するJSにも当てはまります。攻撃者にとってあまり有利な状況ではなさそうですが、逆にこの壊れたオリジンを利用する方法があれば話は別です。
そのために最適なツールはおそらくiframeです。iframe内でリダイレクトさせることで何らかの変化が起きないか調べてみます。攻撃対象と攻撃者のサイトはそれぞれ下記です。
攻撃対象: http://test.mbsd.jp/ (弊社サイトの「X-Frame-Options」無し版)
攻撃者: http://example7.jp/ (iframeに「http://test.mbsd.jp:xyz/」へのリダイレクタを入れる)
下がテスト時の画面キャプチャです。
JSコンソール内のエラーに注目して下さい。iframe内のMBSDのページに含まれている相対URLリソース(/js/jquery.js)が404エラーを起こしているのですが、そのURLが「http://example7.jp/js/jquery.js」になっています。本来これはMBSDサイト(test.mbsd.jp)からロードされるべきものですが、オリジンの異常のせいでiframeの親のサイトからロードしようとしている訳です。
ちなみに、example7.jpに「/js/jquery.js」を置くと、404にならずにきちんとロード/実行されます。
この結果は実は筆者の想定と違ったのですが、それはさておき、下のようなハードコードされた相対URLのシナリオもiframeに入れ込むことによって攻撃可能だということが言えます。
<script src="/js/jquery.js">
(ただし「//example3.jp/a.js」のようなホスト名付きの相対URLは、このバグの影響を受けません)
既に見たように対象ページのJavaScriptは壊れたコンテキストで実行されます。したがって、攻撃者のJSはCookieにアクセスできませんし、同じサイトのページをXHRで取得することもできません。
しかし、JSから自身のページのコンテンツにアクセスすることはできます。つまり、Locationで直接遷移するページについてはその中身を窃取・改竄できるということです。前述の通り要求メッセージにはCookieが含まれるため、認証後のページも攻撃対象になりえます。
最後に、このバグについてのもう少し詳細な情報を下に記しておきます。
・Safariは不正なポートをデフォルトポート(80, HTTPSなら443)とみなす。
BurpやSquidなどのHTTP Proxyがあると、ポート不正によりエラーとなり攻撃は失敗する。
・不正なHostヘッダ(例:「Host: hostname:xyz」)がサーバに送られる。
Apache, WebLogic, Nginxはそのようなヘッダを受け付けるが、Tomcat, IISは受け付けない。
・302/307のリダイレクトにより、HTTPメソッドはGET/POSTのどちらにもなりうる。
ただしリダイレクトのため要求Bodyは常に空となる。
・Base URLは、iframe内であれば親から継承され、そうでなければnullとなる。
何故か「<base href=」は仮に存在していても無視される。
・JSは空の(iframeの親からも隔離された)オリジンで実行される。
CookieとLocalStorage以外のDOMオブジェクトはアクセス可能。
・CSP(またはX-Frame-Options)により防御できる場合がある。