長谷川 武
この何年かでWebアプリケーションは、サーバ上のみでプログラムが動作するものから、多くのJavaScriptコードがWebブラウザで動作し、背後でサーバと連携して、リッチかつスムーズな操作感を提供するものへと進化してきた。このことは、Webブラウザ上に悪意のスクリプトを送り込む「スクリプト注入(XSS)」攻撃を、より防止しづらく、悪用の懸念がより大きなものに変化させた。本稿は、WebアプリケーションにAjax等の進化をもたらした技術について振り返るとともに、スクリプト注入対策を考慮すべき新たな文脈について論じるものである。
Ajax(Asynchronous JavaScript and XML)[1]は、必要となるデータを非同期でWebサーバから取り寄せつつ、Webブラウザの画面上でなめらかな操作性を提供する形態のJavaScriptプログラムのことである。かつてのブラウザにおいてはこのようなことはできなかったが、ある時点からWebブラウザが装備するJavaScript向けAPIが進化し、可能になった。この、JavaScriptの中からのWebサーバとの間の非同期の通信を可能にしたAPIは、XMLHttpRequestという組み込みオブジェクトである(文書上においてはXHRと略されることもある)。最初Internet Explorerに登場し、やがて他のブラウザにも広まっていった。現在XMLHttpRequestオブジェクトには「レベル2」と呼ばれる仕様のものが登場し、さらなる進化を続けている。
図1に、主なブラウザがXMLHttpRequestをサポートした年次を示す。
図1:JavaScript向けに非同期通信APIが登場
Ajaxの登場以前のWebアプリケーションは、ユーザにとって、必ずしもスムーズとは言えない操作性のものだった。
ブラウザの画面でボタンやリンクをクリックすると、Webサーバから応答が返されて画面が再描画されるまで、ユーザは待たされる。表示される情報の変化がごく小さいものであっても、画面(もしくはひとつのフレーム)全体の書き換えが必要だった。プログラマにとっても自由度が低かった。ブラウザ上でJavaScriptプログラムを動作させることはできたが、JavaScriptプログラムからWebサーバへリクエストを発信すると、その時点でJavaScriptプログラムは終了してしまう。呼び出した新しいWebコンテンツに場所を明け渡さなくてはならなかったのである。
上記の不便は、JavaScriptに非同期通信を許すXMLHttpRequestオブジェクトの登場によって(すべてではないが大幅に)解消された。JavaScriptプログラムを終わらせなくても処理の途中でWebサーバへデータを要求できるようになったことの恩恵は大きい。DOM(Document Object Model)のAPIを使って画面の一部分のみ表示内容を変更するテクニックが以前から使えたが、これをWebサーバとの非同期通信と組み合わせられるからである。画面の背後で必要なデータのみをサーバから受け取り、画面の再描画を最小限の範囲で行うことによって、なめらかな操作性が実現する。
当初のXMLHttpRequestには、セキュリティへの配慮から、"Same Origin Policy"(同一源泉ポリシ)と呼ばれる制約が課されていた。XMLHttpRequestオブジェクトを使ってリクエストを送信できる先は、現在のWebコンテンツの出身のWebサーバに限る、というものである。
Ajaxは、その名の由来が示すとおり、非同期でWebサーバから受け取るレスポンスのデータフォーマットにXMLを用いることが当初想定されていた。しかし、XMLにはデータの記述に比較的多くの文字数を要し、Webサーバとのデータの送受信にはXMLのもつ高度な記述能力は必ずしも必要とされない。最近ではXMLに代わり、文法がより単純で記述のための文字数も少なくて済む、JSON(JavaScript Object Notation)と呼ばれるフォーマットが使われるようになってきた。
その名のとおりJSONは、JavaScriptと縁が深い。JSONは、JavaScriptのソースコード内でオブジェクトの値を表す構文を、プログラム外でデータ交換するためのフォーマットに転用したものである。
JavaScriptの規格であるECMAScript edition 5(2009年12月)においては、JSON文字列データとJavaScriptオブジェクトとの間の相互変換のAPIが用意された。
・数値 1701 -2.78 3.6e-12
・文字列 "hello" "?""
・真偽値 true false
・ナル null
・配列 ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
・ハッシュ {"name": "浦島太郎", "age": 256, "phone": null, "real": false}
{"books": [
{"subject": "Rama II", "authors": ["Clarke", "Lee"], "issued": "1 Nov 1990"},
{"subject": "Orion", "authors": ["Kirk", "Scott"], "issued": "1 Apr 2264"},
{"subject": "Android", "authors": ["Data", "Laforge"], "issued": "stardate 47232.1"}]}
最近は、既存のWeb API(Webサービス)を積極的に利用して作られる「マッシュアップ」タイプのWebアプリケーションが増えてきた。マッシュアップタイプのWebアプリケーションとは、外見は一貫性のあるデザインをもち一枚岩のように見せつつ、内部は、異なる主催者が提供するWeb API(Webサービス)を(場合によっては複数)組み合わせて作られるものを指す。この方法をとることによって、既存のWebサイトにさらなる付加価値を付ける別のサイトを短期間で立ち上げられる等のメリットがある。
Ajaxの形態でマッシュアップタイプのWebアプリケーションを構築しようとする際に邪魔になるのがXMLHttpRequestの"Same Origin Policy" だ。従来のXMLHttpRequestにおいては、HTTPリクエストを送ることが可能な先は、自分の「出身地」のWebサーバのみに限られるからである。現在いくつかのブラウザにおいては、XMLHttpRequestレベル2がサポートされ、「出身地」とは異なるドメインのWebサーバへもリクエストを送れるようになった(クロスドメイン呼び出し)[2]。しかし、2009年になるまでそれはできなかった。そこで、一部のプログラマたちはXMLHttpRequestを使わずして非同期通信を行う手法を見つけ、利用してきた経緯がある。そのひとつがJSONP(JSON with Padding)[3]と呼ばれる手法だ。JSONPによるクロスドメインの非同期通信は次のように行われる。
(1) <script src="uri">タグをDOMへ動的に追加して、他ドメインのWebサーバへス
クリプトのコンテンツを要求する形のWeb API呼び出しを行う。このときのuri
には、当該APIに引き渡す必要のあるパラメータを含めておく
(2) そのAPIが返すレスポンスは、次のような関数呼び出しの形のスクリプト文字列になるようにする
callback_func("JSON形式データ")
(3) 呼び出し側に callback_func()関数の実体を用意しておき、Webサーバから返され
た"JSON形式データ"を処理するようにする。なお、callback_funcの関数名は、
(1)の段階で例えば、?callback=callback_func のようなパラメータでAPI側へ受
け渡すことが多い
レスポンスにJSON形式データのみならず、それを処理する関数呼び出しも「追加の詰めもの」(padding)として含める形をとることからJSONPの呼び名ができた。この方法は任意のサーバとのやりとりができて便利である反面、相手が悪意あるサーバの場合、ブラウザを侵害するスクリプトが送られてきて実行されるという、大きなリスクを抱えている。
2009年になって、Firefox, Google Chrome, Safari, IE 8等のブラウザが相次いで、XMLHttpRequest レベル2をサポートし始めた(IE 8においてはXDomainRequestというオブジェクト)。XMLHttpRequestレベル2においてはクロスドメイン呼び出しが解禁された。ただし、無差別にクロスドメイン呼び出しを許すのは危険であるので、同時に、呼び出しの許可や制限を行うためのAccess-Control-Allow-Origin等のレスポンスヘッダが導入された。
例えば、次のようなレスポンスヘッダをWebサーバが返してくる場合、
ここに示された 呼び出し側ドメイン に該当する「出身地」のコンテンツからは当該サーバへクロスドメイン呼び出しが許され、他は許されない。悪意あるマッシュアップサイトが善意のWeb APIサイトを悪用してユーザに詐欺をはたらく、といったケースはこの制約である程度防げると期待できる。お気づきのとおり、これらのレスポンスヘッダはWeb API側が「呼び出し元」に対して制約をかけるものである。呼び出し側が「呼び出せる先」の範囲を制約するような仕組みはいまのところ用意されていない。呼び出し側が何らかの干渉を受け、悪意のWeb APIを誤って呼び出すよう仕向けられた場合には、被害が生じるおそれがある。
Ajaxアプリケーションにおいては当然のことながらJavaScriptが多用される。ユーザに提供する機能が高度になるにつれ、使われるJavaScriptコードも大規模かつ複雑になってきている。そのようなWebコンテンツの中へひとたび「悪意のスクリプト」が紛れ込めば、機密情報の漏えい、重要なデータの改ざん、業務妨害をはじめとする、望ましくない事態の発生が危惧される。しかも、悪意のスクリプトがコンテンツに紛れ込む手口は従来のスクリプト注入(XSS)攻撃とは事情が違ってきている。Ajaxアプリケーションに悪意のスクリプトが侵入する攻撃は、概ね次のいずれかの形で起こる。
(1) 信頼できない第三者スクリプトのロード。第三者のWebサイトから取り込んだ
JavaScriptプログラムに悪意のスクリプトが潜んでいる
(2) JSONPによるスクリプト侵入。JSONPを用いた第三者のWeb API呼び出しへの
応答として悪意のスクリプトが返される
(3) 従来型のスクリプト注入(XSS)。サーバ側プログラムの不備によって、Webペー
ジ内に悪意のスクリプトが侵入する
(4) DOMベースのスクリプト注入(XSS)[4]。ブラウザ上のJavaScriptプログラムが、
DOM上の危険な入力地点(ソース)から得たデータに含まれる攻撃パターンをそ
のままにして危険な出力地点(シンク)へ送り出すことで、Webページ内に悪意
のスクリプトが侵入する
従来型のスクリプト注入攻撃は、HTMLのタグの中に<script>...</script>のようなスクリプトタグを紛れ込ませて悪意のスクリプトを実行させるものだった。
図2に従来型のスクリプト注入(XSS)攻撃の図式を示す。ここでは、サーバ側プログラムの「ソース」から入ってくる攻撃パターンを見過ごして「シンク」へそのまま出力することで攻撃が成立する。したがって対策は、「シンク」に危険な内容が出力されないよう、サーバ側プログラムの中で特殊記号を無害化する等、比較的単純なもので済んでいた。
図2: 従来型のスクリプト注入(XSS)攻撃
DOMを舞台とするスクリプト注入(XSS)攻撃においてはだいぶ事情が違ってきている。Ajaxタイプのプログラムの動作は、ブラウザ上で動くJavaScriptによって動的にDOMが書き換えられつつ進行する。そのため、Webページ内への不正なスクリプトの侵入は、ブラウザ上のJavaScriptプログラムが、DOM上の危険な入力地点(ソース)から得たデータに含まれる攻撃パターンをそのままにして危険な出力地点(シンク)へ送り出すことで、Webページ内に悪意のスクリプトが侵入する形をとる[4]。
図3: DOMを舞台とするスクリプト注入(XSS)攻撃
マッシュアップ・アプリケーション(他者が用意したWeb APIを積極的に利用するスタイルのアプリケーション)のばあい、第三者のWebサービスからデータやスクリプトを受け取る場面で、予期せぬ攻撃や脆弱性を呼び込むおそれがある。
DOMベースのスクリプト注入(XSS)に関して警戒すべき入力地点(ソース)には、例えば、次のものが考えられる。
(1) コンテンツをロードした際のURIの構成要素
|
DOMベースのスクリプト注入(XSS)に関して警戒すべき出力地点(シンク)には、例えば、次のものが考えられる。
(1) 信頼できない第三者スクリプトを含むおそれのあるコンテンツのロード
|
DOMベースのスクリプト注入(XSS)対策としてクライアント側で動作するプログラムの中で次の事項を行う。
|
(1) 十分信頼できるWebサイト以外からJavaScriptファイルをロードしない。 (2) 十分信頼できるWebサービス以外をJSONPで呼び出さない。 (3) JSON形式文字列データをJavaScriptオブジェクトに復元するには、eval()関数 出す値については次のようにする。
b) 他に方法がなく、警戒すべき入力地点(ソース)から得た値を渡さざるを得な い場合、実行されることになるスクリプト文字列をチェックし、望ましい振 る舞いをすることが確認できたもののみを渡す。
b) > → > c) " → " d) ' → ' e) & → & たしているか厳しくチェックする。例えば次のような観点でチェックする。
b) 桁数の範囲 c) 数値の範囲 d) 特定のリストに含まれる 等 |
上記に加えて従来型のスクリプト注入対策も、もちろん、必要である。
|
(7) サーバ側プログラムにスクリプト注入(XSS)対策を施す。 |
ブラウザベンダによるスクリプト注入(XSS)対策も進められている。ここでは主なものをふたつ紹介する。
(1) X-Content-Security-Policy
|
Content Security Policyは、Firefox 4以降(厳密にはHTMLレンダリングエンジンGecko 2.0以降を使用するブラウザ)が備えるスクリプト注入(XSS)対策機能である[5][6]。
これは、ブラウザがどこからスクリプトをロードするかについて、整然と制約を設けることによって、信頼できないサイトからのスクリプトの混入を防ごうというものである。
この対策機能は、
この仕様には複雑な指定が可能であるが、最も単純なものは、例えば、
なお、X-Content-Security-Policyレスポンスヘッダは、多くのパラメータもっていて、よりきめ細かな指定をすることができる。
X-Content-Security-Policy: allow ホスト...
[; options {inline-script | eval-script}...]
[; img-src ホスト...][; media-src ホスト...]
[; srcipt-src ホスト...][; object-src ホスト...]
[; frame-src ホスト...][; font-src ホスト...]
[; xhr-src ホスト...][; frame-ancesors ホスト...]
[; style-src ホスト...]
[; report-uri uri][; policy-uri uri]
|
XSS Filterは、Internet Explorer 8(以下IE 8)以降が備えているスクリプト注入(XSS)対策機能である[7][8]。類似の対策機能は、Google ChromeやSafariの新しいバージョンにも備わっている。
これは、HTTPリクエストに含まれていたスクリプト注入(XSS)攻撃パターンがサーバからそのままHTTPレスポンスに出力されてきたら、そのスクリプトを動作させないというものである。この対策機能は、例えば、
| (1) 機能が活性化する条件 | ||
次のいずれかの指定/設定によって、XSS Filter機能がオンになる
X-XSS-Protection: 1; mode=block
コントロールパネル > インターネットオプション > セキュリティ > 該当するゾーン > レベルのカスタマイズ > XSSフィルターを有効にする > 有効にする |
||
| (2) 検出/防御してくれるもの | ||
次の両方が成り立つと判定したとき、XSS Filterは防御効果を発揮する
例えば、次のようなリクエストデータ、
をHTTPレスポンス内にエコーさせると、ブラウザのHTMLソース上にはそのまま置かれるが、注入されたスクリプトは実行されない。
をHTTPレスポンス内にエコーさせると、次の、
のように書き換えられ、HTMLソースの記述上も注入されたスクリプトが無効にされる。
が指定されたときに、攻撃パターンがあると判定されると、IE8(及びそれ以降)においては、HTTPレスポンスのコンテンツ全体が
ただひとつに置き換えられる。Google ChromeとSafariにおいては、空白のページが表示される。 | ||
注意すべき点として、このXSS Filterでは、次のものを阻止できないことがあげられる。
Ajaxの進歩とマッシュアップ開発スタイルの発展に伴い、Webアプリケーションのブラウザ側のプログラミングは複雑になった。Webコンテンツの中への悪意のスクリプトの侵入の手口も多様化している。かつては、予定外のスクリプトが入り込まないよう、Webページの保護につとめればよかった。しかし今や、クライアント側の、DOMから値を取り出す箇所、DOMの一部を書き換える箇所のそれぞれにおける警戒と対策が必要である。攻防は、いわば「DOMがかたちづくるジャングル」の中のゲリラ戦に移ってきたといってよい。
その一方、ブラウザのスクリプト注入(XSS)対策機能も進歩してきている。Firefox 4 [9]以降がもつContent Security Policyは、インラインスクリプトや他ホストからのスクリプトのロードを抑制でき、DOMベースのスクリプト注入(XSS)に効果を発揮する。
Internet Explorer 8 [10]以降(およびGoogle Chrome[11]、Safari[12]の新しいバージョン)がもつXSS Filterは、DOMベースのスクリプト注入(XSS)対策には不足があるものの、有用な対策機能のひとつである。
以上
| Ajax | |
| [1] | "Ajax - Wikipedia" http://ja.wikipedia.org/wiki/Ajax |
| [2] | "Cross-Origin Resource Sharing - Wikipedia, the free encyclopedia" http://en.wikipedia.org/wiki/Cross-Origin_Resource_Sharing |
| [3] | "JSONP - Wikipedia“ http://ja.wikipedia.org/wiki/JSONP |
| DOMベースのスクリプト注入(XSS) | |
| [4] | "DOM Xss Identification and Exploitation“ http://dominator.googlecode.com/files/DOMXss_Identification_and_exploitation.pdf |
| Content Security Policy - Firefox(Gecko 2.0)のXSS対策機能 | |
| [5] | "Introducing Content Security Policy - MDC Docs" https://developer.mozilla.org/en/Introducing_Content_Security_Policy |
| [6] | "Using Content Security Policy - MDC Docs" https://developer.mozilla.org/en/Security/CSP/Using_Content_Security_Policy |
| XSS Filter - IE 8以降、およびGoogle Chrome、SafariのXSS対策機能 | |
| [7] | "IE8 Security Part IV: The XSS Filter" http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx |
| [8] | "IE8 XSS filter: what does it really do?" http://stackoverflow.com/questions/2051632/ie8-xss-filter-what-does-it-really-do |
| ブラウザのバージョン履歴 | |
| [09] | "Old Version of Firefox 1.0 Download - OldApps.com" http://www.oldapps.com/firefox.php?old_firefox=48 |
| [10] | "History of Internet Explorer - Wikipedia, the free encyclopedia" http://en.wikipedia.org/wiki/History_of_Internet_Explorer |
| [11] | "Old Version of Google Chrome Download - OldApps.com" http://www.oldapps.com/google_chrome.php |
| [12] | "Safari version history - Wikipedia, the free encyclopedia" http://en.wikipedia.org/wiki/Safari_version_history |