知っていれば恐くない、XMLHttpRequestによるXSSへの対応方法:HTML5時代の「新しいセキュリティ・エチケット」(3)(2/2 ページ)
“新しいXSS”は知識こそが対策の第一歩。基本の対策を行うことが重要です。XMLHttpRequestによるXSSも例外ではありません。
XHRによってやり取りされるデータによるXSS
皆さんご存じの通り、Internet Explorer(以下、IE)ではContent-Typeに従わずにコンテンツをHTML扱いすることがありますので、XHRでやり取りされるデータを直接IEで開いた場合にXSSが発生することがあります。
【関連記事】
教科書に載らないWebアプリケーションセキュリティ(2):
[無視できない]IEのContent-Type無視
http://www.atmarkit.co.jp/ait/articles/0903/30/news118.html
例えば、XHRでJSONをやり取りしているWebアプリケーションにおいて、攻撃者がJSON内にHTML断片を意味する文字列を挿入可能であったとします。
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{ "msg" : "<script>alert(1)></script>" }
このJSONをhttp://example.jp/sample.json/a.htmlのように、URLの後ろに「/a.html」を付け足してIEで開くと、コンテンツがJSONではなくHTMLとして解釈され、JavaScriptが動作してしまいます。
JSONであれば、文字列内の「<」「>」などを「\u003c」「\u003e」のようにエスケープすることもできますが、XHRでやり取りしているデータがJSONではなく、CSVやプレーンテキストの場合は、文字列をエスケープすることはできません。
このようなXHRでやり取りされるデータを使ったXSSを防ぐには、(1) レスポンスヘッダーに「X-Content-Type-Options: nosniff」を付与し、(2)XHRからのリクエストにのみ応答する、の両方の方法を採る必要があります。
(1)レスポンスヘッダーに「X-Content-Type-Options: nosniff」を付与
IE8以降では、コンテンツにレスポンスヘッダーとして「X-Content-Type-Options: nosniff」をつけることで、リソースのContent-Typeを無視することがなくなり、HTMLではないものをHTML扱いしてしまうことによるXSSを防ぐことができます。
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff
{ "msg" : "<script>alert(1)</script>" }
このように、リクエストヘッダーに「X-Content-Type-Options: nosniff」を含むコンテンツを、IE8以降で開いた場合には、Content-Typeがtext/htmlではないためHTML扱いされることはなくなり、XSSが発生することもありません。
HTMLではないものをHTML扱いしなくなるだけではなく、例えばHTML(text/html)やJSON(application/json)のようなContent-Typeを持つリソースを<script src>で指定した場合にJavaScriptとして解釈することもなくなるため、そのような手法による情報漏えいを未然に防ぐことにもつながります。
X-Content-Type-Optionsヘッダーは大きな副作用もなく、付与するだけで安全性を高めることができますので、動的に生成されるコンテンツ全てに付けておくことを強く推奨します。
ただし、X-Content-Type-OptionsレスポンスヘッダーはIE8以降で有効なものであり、IE6およびIE7では対応していないため、IE6およびIE7を使用するユーザーに対してもXSSの保護を提供するのであれば、次に示す「XHRからのリクエストのみ応答する」という対策も併せて必要となります。
(2)XHRからのリクエストのみ応答する
XHRでやり取りされるデータを、XHRからのリクエストの時のみ応答し、ブラウザーから直接開かれた場合には応答しないようにすることで、Content-Typeを無視して発生するXSSを防ぐことができます。
リクエストがXHRからであることを明示するために、XHRではリクエスト時にカスタムヘッダーを付与します。
var xhr = new XMLHttpRequest(); xhr.open( "GET", "http://example.jp/sample.json", true ); xhr.onreadystatechange = function(){ ... }; xhr.setRequestHeader( "X-Requested-With", "XMLHttpRequest" ); xhr.send();
このとき、実際に送信されるリクエストは以下のようになります。
GET http://example.jp/sample.json HTTP/1.1
Host: example.jp
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1)
Connection: keep-alive
サーバー側では、リクエストヘッダーに「X-Requested-With: XMLHttpRequest」というカスタムヘッダーが含まれていることを確認することで、ブラウザーで直接コンテンツを開いたのではなく、XHR経由でのリクエストであることを確認できます。XHRの場合にのみ正規のJSONを応答し、そうでない場合には403や500などの応答を返すことで、ブラウザーで直接コンテンツを閲覧することを防ぐことができます。
なお、jQueryやprototype.jsなどのJavaScriptライブラリでは、自動的に上記のリクエストヘッダーを付与してくれます。
このように、XHRを使ってのデータをやり取りする周辺では、XSSが発生しやすくなります。まとめると、以下の2点がポイントになります。
- 「X-Content-Type-Options: nosniff」ヘッダーを全ての動的生成されるコンテンツに付与する
- XHRでやり取りするデータは、カスタムのリクエストヘッダーを付けることによりXHRでのみ応答するようにする
次回は、前回および今回の記事でも少し触れた「オープンリダイレクター」について解説します。
はせがわようすけ プロフィール
ネットエージェント株式会社 エバンジェリスト、株式会社セキュアスカイ・テクノロジー 技術顧問。Internet Explorer、Mozilla FirefoxをはじめWebアプリケーションに関する多数の脆弱性を発見。Black Hat Japan 2008、韓国POC 2008、2010他講演多数。
Copyright © ITmedia, Inc. All Rights Reserved.