6.6. XSS対策

6.6.1. Overview

クロスサイトスクリプティング(以下、XSSと略す)とは、アプリケーションのセキュリティ上の不備を意図的に利用し、
サイト間を横断して悪意のあるスクリプトを混入させることである。
例えば、ウェブアプリケーションが入力したデータ(フォーム入力など)を、適切にエスケープしないまま、HTML上に出力することにより、
入力値に存在するタグなどの文字が、そのままHTMLとして解釈される。
悪意のある値が入力された状態で、スクリプトを起動させることにより、クッキーの改ざんや、
クッキーの値を取得することによる、セッションハイジャックなどの攻撃が行えてしまう。

6.6.1.1. Stored, Reflected XSS Attacks

XSS攻撃は、大きく分けて二つのカテゴリに分けられる。

Stored XSS Attacks

Stored XSS Attacksとは、悪意のあるコードが、永久的にターゲットサーバ上(データベース等)に格納されていることである。
ユーザは、格納されている情報を要求するときに、サーバから悪意のあるスクリプトを取得し、実行してしまう。

Reflected XSS Attacks

Reflected attacksとは、リクエストの一部としてサーバに送信された悪意のあるコードが、
エラーメッセージ、検索結果、その他いろいろなレスポンスからリフレクションされることである。
ユーザが、悪意のあるリンクをクリックするか、特別に細工されたフォームを送信すると、
挿入されたコードは、ユーザのブラウザに、攻撃を反映した結果を返却する。
その結果、信頼できるサーバからきた値のため、ブラウザは悪意のあるコードを実行してしまう。

Stored XSS Attacks、Reflected XSS Attacksともに、出力値をエスケープすることで防ぐことができる。


6.6.2. How to use

ユーザーの入力を、そのまま出力している場合、XSSの脆弱性にさらされている。
したがって、XSSの脆弱性に対する対抗措置として、HTMLのマークアップ言語で、特定の意味を持つ文字をエスケープする必要がある。

必要に応じて、3種類のエスケープを使い分けること。

エスケープの種類:

  • Output Escaping
  • JavaScript Escaping
  • Event handler Escaping

6.6.2.1. Output Escaping

XSSの脆弱性への対応としては、HTML特殊文字をエスケープすることが基本である。
エスケープが必要なHTML上の特殊文字の例と、エスケープ後の例は、以下の通りである。
エスケープ前 エスケープ後
& &
< &lt;
> &gt;
" &quot;
' &#39;
XSSを防ぐために、文字列として出力するすべての表示項目に、f:h()を使用することを必須とする。
入力値を、別画面に再出力するアプリケーションを例に、説明する。|

6.6.2.1.1. 出力値をエスケープしない脆弱性のある例

本例は、あくまで参考例として載せているだけなので、以下のような実装は、決して行わないこと。

出力画面の実装

<!-- omitted -->
<tr>
    <td>Job</td>
    <td>${customerForm.job}</td>  <!-- (1) -->
</tr>
<!-- omitted -->
項番 説明
(1)
customerFormのフィールドである、jobをエスケープせず出力している。
入力画面のJobフィールドに、<script>タグを入力する。
input_html_tag

Picture - Input HTML Tag

<script>タグとして認識され、ダイアログボックスが表示されてしまう。
no_escape_result

Picture - No Escape Result

6.6.2.1.2. 出力値をf:h()関数でエスケープする例

出力画面の実装

<!-- omitted -->
<tr>
    <td>Job</td>
    <td>${f:h(customerForm.job)}</td>  <!-- (1) -->
</tr>
.<!-- omitted -->
項番 説明
(1)
EL式のf:h()を使用することにより、エスケープして出力している。
入力画面のJobフィールドに<script>タグを入力する。
input_html_tag

Picture - Input HTML Tag

特殊文字がエスケープされることにより、 <script>タグとして認識されず、入力値がそのまま出力される。
escape_result

Picture - Escape Result

出力結果

<!-- omitted -->
<tr>
    <td>Job</td>
    <td>&lt;script&gt;alert(&quot;XSS Attack&quot;)&lt;/script&gt;</td>
</tr>
<!-- omitted -->

Tip

java.util.Date継承クラスのフォーマット

java.util.Date継承クラスをフォーマットして表示する場合は、JSTLの<fmt:formatDate>を用いることを推奨する。 以下に、設定例を示す。

<fmt:formatDate value="${form.date}" pattern="yyyyMMdd" />

valueの値に前述した f:h()を使用して値を設定すると、Stringになってしまい、javax.el.ELExceptionがスローされるため、そのまま${form.date}を使用している。 しかし、yyyyMMddにフォーマットするため、XSSの心配はない。

Tip

java.lang.Number継承クラス、またはjava.lang.Numberにパースできる文字列

java.lang.Number継承クラスまたはjava.lang.Numberにパースできる文字列をフォーマットして表示する場合は、<fmt:formatNumber>を用いることを推奨する。 以下に、設定例を示す。

<fmt:formatNumber value="${f:h(form.price)}" pattern="###,###" />

上記は、Stringでも問題ないので、<fmt:formatNumber>タグを使わなくなった場合に f:h() を付け忘れることを予防するため、f:h()を明示的に使用している。

6.6.2.2. JavaScript Escaping

XSSの脆弱性への対応としては、JavaScript特殊文字をエスケープすることが基本である。
ユーザからの入力をもとに、JavaScriptの文字列リテラルを動的に生成する場合に、エスケープが必要となる。
エスケープが必要なJavaScriptの特殊文字の例と、エスケープ後の例は、以下のとおりである。
エスケープ前 エスケープ後
' \'
" \"
\ \\
/ \/
< \x3c
> \x3e
0x0D(復帰) \r
0x0A(改行) \n

6.6.2.2.1. 出力値をエスケープしない脆弱性のある例

XSS問題が発生する例を、以下に示す。
<html>
  <script  type="text/javascript">
      var aaa = '<script>${warnCode}<\/script>';
      document.write(aaa);
  </script>
<html>
属性名
warnCode <script></script><script>alert('XSS Attack!');</script><\/script>
上記例のように、ユーザーの入力を導出元としてコードを出力するなど、
JavaScriptの要素を動的に生成する場合、意図せず文字列リテラルが閉じられ、XSSの脆弱性が生じる。
javascript_xss_screen_no_escape_result

Picture - No Escape Result

出力結果

<script type="text/javascript">
    var aaa = '<script><\/script><script>alert('XSS Attack!');<\/script><\/script>';
    document.write(aaa);
</script>

Tip

業務要件上必要でない限り、JavaScriptの要素をユーザからの入力値に依存して動的に生成する仕様は、 任意のスクリプトが埋め込まれてしまう可能性があるため、別の方式を検討する、または、極力避けるべきである。

6.6.2.2.2. 出力値をf:js()関数でエスケープする例

XSSを防ぐために、ユーザの入力値、が設定される値にEL式の関数、f:js()の使用を推奨する。

使用例を、下記に示す。

<script type="text/javascript">
  var message = '<script>${f:js(message)}<\/script>';  // (1)
  <!-- omitted -->
</script>
項番 説明
(1)
EL式のf:js()を使用することにより、エスケープして変数に設定している。

出力結果

<script  type="text/javascript">
    var aaa = '<script>\x3c\/script\x3e\x3cscript\x3ealert(\'XSS Attack!\');\x3c\/script\x3e<\/script>';
    document.write(aaa);
</script>

6.6.2.3. Event handler Escaping

javascript のイベントハンドラの値をエスケープする場合、f:h()や、f:js()を使用するのではなく、
f:hjs()を使用すること。${f:h(f:js())}と同義である。
理由としては、 <input type="submit" onclick="callback('xxxx');">のようなイベントハンドラの値に
"');alert("XSS Attack");// "を指定された場合、別のスクリプトを挿入できてしまうため、
文字参照形式にエスケープ後、HTMLエスケープを行う必要がある。

6.6.2.3.1. 出力値をエスケープしない脆弱性のある例

XSS問題が発生する例を、以下に示す。
<input type="text" onmouseover="alert('output is ${warnCode}') . ">
属性名
warnCode
'); alert('XSS Attack!'); //
上記の値が設定されてしまうことで、意図せず文字列リテラルが閉じられ、XSSの脆弱性が生じる。
マウスオーバ時、XSSのダイアログボックスが表示されてしまう。
eventhandler_xss_screen_no_escape_result

Picture - No Escape Result

出力結果

<!-- omitted -->
<input type="text" onmouseover="alert('output is'); alert('XSS Attack!'); // .') ">
<!-- omitted -->

6.6.2.3.2. 出力値をf:hjs()関数でエスケープする例

使用例を、下記に示す。

<input type="text" onmouseover="alert('output is ${f:hjs(warnCode)}') . ">  // (1)
項番 説明
(1)
EL式のf:hjs()を使用することにより、エスケープして引数としている。
マウスオーバ時、XSSのダイアログは出力されない。
eventhandler_xss_screen_escape_result

Picture - Escape Result

出力結果

<!-- omitted -->
<input type="text" onmouseover="alert('output is \&#39;); alert(\&#39;XSS Attack!\&#39;);\&quot; \/\/ .') ">
<!-- omitted -->