第8章 マッシュアップ
#3 クライアント側コードに起因するスクリプト注入対策
近年、Webアプリケーションの構成が変化しつつある。多くのJavaScriptコードがWebブラウザで動作するようになり、その背後ではサーバと連携してリッチかつスムーズな操作感を提供するような構成が普及している。いわゆる Ajaxタイプのコンテンツが増えている。
このような環境において、WebクライアントのJavaScriptプログラムを標的とするスクリプト注入攻撃がありうる。いわゆる「DOMベースのスクリプト注入」である。この攻撃についての対策を解説する。
ブラウザ内のJavaScriptランタイム環境についての基礎知識
いわゆる「DOMベースのスクリプト注入」攻撃は、従来のWebサーバ側プログラムのエコーバック(鸚鵡返し)ロジックを悪用するスクリプト注入攻撃とは事情が異なる。ブラウザ内で実行され、サイト横断的な構図にはならない。DOM等のブラウザオブジェクトの細部についての理解が不可欠である。
Ajaxタイプの動的コンテンツは、ユーザによる画面操作の解釈と応答の多くをコンテンツ内のJavaScriptプログラムで行う。ユーザへの応答の画面表示を行うために、JavaScriptプログラム内部では、DOMを含むブラウザオブジェクトツリーの一部分を動的に書き換えることが行われる。
DOMオブジェクトツリーを構成する document オブジェクトは、window 配下にある。window オブジェクトが、ブラウザによって表示されているウィンドウに対応するグローバルオブジェクトなのである。window の location プロパティが location オブジェクトであり、現在表示されているURI に関する情報が格納される。location オブジェクトが更新されれば document オブジェクト全体が更新される関係にある。
JavaScriptプログラムは、DOM、ブラウザオブジェクト、ECMAScriptオブジェクトを操作する。この際に API を直接操作することもできるが、jQuery のライブラリを用いて操作するのが効率的であろう。
クライアント側コードに起因するスクリプト注入のメカニズム
いわゆる「DOMベースのスクリプト注入」を行う代表的な手口は、書き換えの際にオブジェクトツリーに悪意のスクリプト要素を接続させて、それを実行させるようにするものである。
例えば、コンテンツが呼び出された際のURI上のクエリ文字列(?xxxの部分)を自ら取り込み Query: [ … ] と表示することを意図した次のようなHTMLコンテンツがあるとする。
… Query: [<span id="span1"></span>] … <script> var span1 = document.getElementById ("span1"); var query = decodeURI (location.search).substr (1); span1.innerHTML = query;←※1 </script>
これを次のようなURIで呼び出す。
http://ホスト/パス?<img src=a onerror="alert(3)"></img>
実際には適切なエンコードを施すものとする。すると、innerHTMLプロパティへの代入(※1)によって<span>要素へ<img>要素が接続されるがその際に、<img>タグに仕込まれている onerrorハンドラのスクリプトが実行されてしまう。 すなわち、 location.search から悪意のスクリプトが入り、DOM 要素の innerHTML プロパティに注入されてしまうのである。
サーバ上のプログラムが、信頼できない入力データをそのままブラウザへ向けて送り出すと脆弱性が生じるのと同様に、クライアントで動作する JavaScriptプログラムにおいては、信頼できない入力データをそのままオブジェクトツリーに書き込むことによって、スクリプト注入されうる脆弱性が生じる。
複数の侵入経路・複数の発火地点
信頼できないデータが入ってくる経路には複数の種類がありうる。特に、Webページの構成要素のいくつかを、他者運営の必ずしも高い信頼をおけないサーバから取得する場面では、それらに攻撃スクリプトが潜んでいることを警戒する必要がある。
また、侵入してきた攻撃パターンがスクリプトとして作動してしまう場面にも複数の種類がある。
jQueryによる無防備でないDOM操作
jQuery は、JavaScript 用ライブラリである。jQuery を使うと、DOM に対する読み出しと変更を手短かに記述できる。
例えば、jQuery を使った次のコードは、Webページの中から class="alfa" の属性をもつタグをすべて見つけ出し、それらのテキストをすべて hello に設定した上で文字の色を赤くするというものである。:
$(".alfa").text ("hello").css ("color", "red")
jQuery を使用するプログラミングにおいて、スクリプト注入を特に警戒すべきなのは次の3箇所である。:
- $() の引数
- x.html() の引数
- $.parseHTML() の引数
これらの箇所ではHTML文字列の解釈が行われる。ここに信頼できない入力値(location.hash 等)をそのまま渡すと、スクリプト注入を招くおそれがある。
$() の引数
$() は複数の機能を兼ね備えた関数である。中でも主要な機能は、DOMの中を検索して条件に合った要素の集合を得ることと、これからDOMへ追加すべき新たな要素を作り出すことである。(下位要素を従えることができる。)
DOM要素を検索するつもりであっても、引数に次のような文字列が含まれていると、DOM要素を作り出す機能の方が働いて onerror= に仕込まれているスク リプトが動作してしまう。
<img src="/" onerror="...">
対策は、$() の引数にはリテラルのみ与えるようにすることである。変数を含む式を用いて検索条件を与えたい場合は、検索専用の find() を用い、次のように記述する。
$(document).find ( 式 ) もしくは
$("#id").find ( 式 )
x.html() の引数
html() は、$() や $(...).find() によって得られたDOM要素(ここでは x としている)へ下位の要素(タグ)を追加する関数である。引数にHTML文字列を与えるものであるため、スクリプト注入を招くおそれがある。
対策は、タグはリテラルで記述し、可変要素を text() を用いて設定するようにすることである。例えば、
x.html("<ul><li>" + 項目1 + "</li><li>" + 項目2 + "</li></ul>");
とする代わりに、次のように記述する。:
x.empty ().append ( $("<ul></ul>") .append ($("<li></li>").text ( 項目1 )) .append ($("<li></li>").text ( 項目2 )) );
$.parseHTML() の引数
$.parseHTML() は、HTML文字列を処理しその解析結果を返す関数である。次のように用いて新たなDOM要素(下位要素を含む)を生成するのに用いられる。
$($.parsetHTML( HTMLソース文字列 ))
$("<タグ>...") と類似しているが、引数に与える文字列が必ずしも「<」で始まらなくてもよい点が異なる。
この関数も html() と同様、引数にはHTML文字列を与えることになるため、スクリプト注入を招くおそれがある。対策も、html() の対策に準ずる。
クライアント側コードに起因するスクリプト注入対策
クライアント側コードに起因するスクリプト注入に対しては次のように対策する。 :
(1) | 警戒すべき入力地点(ソース)から得た値はどれも、その項目の所定の仕様を満たしているか厳しくチェックする。例えば次のような観点でチェックする。:
a) 文字種 |
(2) | 十分信頼できるWebサイト以外からのJavaScriptファイルはロードしない。 |
(3) | 十分信頼できるWebサービス以外をJSONPで呼び出したりしない。 |
(4) | JSON形式文字列データをJavaScriptオブジェクトに復元するにためは、eval()関数ではなくJSON.parse()メソッドを使う。 |
(5) | eval()関数をはじめとする「文字列がスクリプトとして解釈実行される文脈」に送り出す値については次のようにする。:
a) 原則として、警戒すべき入力地点(ソース)から得た値を渡さない。 |
(6) | 安全にjQueryを呼び出す。(上述(「jQueryによる無防備でないDOM操作(仮)」)のとおり)
要点: a) $(セレクタ) を使わず、代わりに $( ).find(セレクタ) を使う |
(7) | document.write()に渡る値(HTMLと解釈しうるAPIすべてを含む)は、常に定番のサニタイズ処理を施してから引数へ渡す。:
jQuery で書く場合: JavaScript で書く場合: |
当然ながら、上記の対策に加えて従来型のスクリプト注入対策も必要である。
(8) | サーバ側プログラムにスクリプト注入対策を施す。 |
資料:JacaScriptプログラムのソースとシンク
警戒すべきソース(入力地点)
警戒すべきソース(入力地点)には、下記の要素等がある。:
location |
コンテンツのURIを参照/変更できるオブジェクト。値を参照すると、location.hrefと同じ値が得られる。 |
location.hash | コンテンツのURIのフラグメント部分 |
location.search | コンテンツのURIのクエリ部分 |
location.pathname | コンテンツのURIのパス部分 |
location.href | コンテンツのURIの全体 |
document.URL | コンテンツのURI |
document.documentURI | コンテンツのURI。document.URL と同じ値をもつ。DOM 4で提案されている。 |
document.location | location (document. で修飾されない)と同じオブジェクトを指す |
document.baseURI | このコンテンツを起点として相対URIを解決する際に用いられる基準のURI。ドキュメント中に<base>タグ等による明示がないときは、document.URLと同じ値をもつ。 |
document.referrer | このコンテンツへのリンクが書かれていたWebページのURI |
document.cookie | コンテンツに関連づけられているクッキーすべてが並べられた文字列 |
window.name | ウィンドウの名前 |
{onmessageハンドラの引数}.data | Web Messaging受信ハンドラ関数の引数のdataプロパティ |
警戒すべきシンク(出力地点)
警戒すべきシンク(出力地点)には下記のものがある。DOMオブジェクトツリーを構成するdocumentオブジェクト配下以外にも警戒すべきシンクがある。:
(1) | 信頼できない第三者スクリプトを含むおそれのあるコンテンツのロード
|
(2) | 文字列がスクリプトとして解釈実行される文脈への値の送り込み
ECMA仕様に規定されている eval系関数:
ECMA仕様にも W3C仕様にも規定されていないが大部分のブラウザが備えているもの:
Internet Explorer固有のもの:
|
(3) | <script>タグが効力をもつ文脈への値の出力
|