9.7. XSS対策¶
9.7.1. Overview¶
9.7.1.1. Stored, Reflected XSS Attacks¶
XSS攻撃は、大きく二つのカテゴリに分けられる。
Stored XSS Attacks
Reflected XSS Attacks
9.7.1.1.1. How to use¶
必要に応じて、3種類のエスケープを使い分けること。
エスケープの種類:
Output Escaping
JavaScript Escaping
Event handler Escaping
9.7.1.2. Output Escaping¶
エスケープ前
|
エスケープ後
|
---|---|
“
& “ |
& |
“
< “ |
< |
“
> “ |
> |
“
" “ |
" |
“
' “ |
' |
f:h()
を使用すること。th:text
属性、th:utext
属性の二種類が存在する。
th:text
属性を使用すると値をエスケープして出力する
th:utext
属性を使用すると値をエスケープせずに出力する
XSSを防ぐために、Thymeleafではth:text
属性を使用すること。
入力値を別画面に再出力するアプリケーションを例に説明する。
9.7.1.2.1. 出力値をエスケープしない脆弱性のある例¶
本例は、あくまで参考例として載せているだけなので、以下のような実装は決して行わないこと。
出力画面の実装
<!-- omitted -->
<tr>
<td>Job</td>
<td>${customerForm.job}</td> <!-- (1) -->
</tr>
<!-- omitted -->
項番 |
説明 |
---|---|
(1)
|
customerFormのフィールドである、jobをエスケープせず出力している。
|
出力画面の実装
<!--/* omitted */-->
<tr>
<td>Job</td>
<td th:utext="${customerForm.job}">Job</td> <!--/* (1) */-->
</tr>
<!--/* omitted */-->
項番 |
説明 |
---|---|
(1)
|
th:utext 属性を使用することにより、customerFormのフィールドであるjobをエスケープせず出力している。 |
入力画面のJobフィールドに、<script>タグを入力する。
9.7.1.2.2. 出力値をf:h()関数でエスケープする例¶
出力画面の実装
<!-- omitted -->
<tr>
<td>Job</td>
<td>${f:h(customerForm.job)}</td> <!-- (1) -->
</tr>
<!-- omitted -->
項番 |
説明 |
---|---|
(1)
|
EL式の
f:h() を使用することにより、エスケープして出力している。 |
出力画面の実装
<!--/* omitted */-->
<tr>
<td>Job</td>
<td th:text="${customerForm.job}">Job</td> <!--/* (1) */-->
</tr>
<!--/* omitted */-->
項番 |
説明 |
---|---|
(1)
|
th:text 属性を使用することにより、エスケープして出力している。 |
入力画面のJobフィールドに<script>タグを入力する。
出力結果
<!-- omitted -->
<tr>
<td>Job</td>
<td><script>alert("XSS Attack")</script></td>
</tr>
<!-- omitted -->
Tip
java.util.Date継承クラスのフォーマット
java.util.Date継承クラスをフォーマットして表示する場合は、JSTLの<fmt:formatDate>
を用いることを推奨する。
以下に、設定例を示す。
<fmt:formatDate value="${form.date}" pattern="yyyyMMdd" />
valueの値に前述した f:h()
を使用して値を設定すると、Stringになってしまい、jakarta.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()
を明示的に使用している。
出力結果
<!-- omitted -->
<tr>
<td>Job</td>
<td><script>alert("XSS Attack")</script></td>
</tr>
<!-- omitted -->
Note
インライン記法を使用する場合
Thymeleafでテキストを出力する方法にはth:text
、th:utext
の他にインライン記法が存在する。
インライン記法については、JavaScriptテンプレートの適用のインライン記法の項を参照されたい。
9.7.1.3. JavaScript Escaping¶
JavaScriptにおいてエスケープが必要な特殊文字の例と、エスケープ後の例は、以下のとおりである。
エスケープ前
|
エスケープ後
|
---|---|
“
' “ |
\' |
“
" “ |
\" |
“
\ “ |
\\ |
“
/ “ |
\/ |
“
< “ |
\x3c |
“
> “ |
\x3e |
0x0D(復帰) |
\r |
0x0A(改行) |
\n |
9.7.1.3.1. 出力値をエスケープしない脆弱性のある例¶
XSS問題が発生する例を、以下に示す。
本例は、あくまで参考例として載せているだけなので、以下のような実装は決して行わないこと。
<html>
<script type="text/javascript">
var aaa = '${warnCode}';
alert(aaa);
</script>
</html>
属性名 |
値 |
---|---|
warnCode
|
';alert('XSS Attack!');aaa='message |
<html>
<script type="text/javascript">
var aaa = "[(${warnCode})]"; <!--/* (1) */-->
alert(aaa);
</script>
</html>
項番 |
説明 |
---|---|
(1)
|
[(xxx)] の形式を用いたインライン記法により、warnCode をエスケープせず出力している。 |
属性名 |
値 |
---|---|
warnCode
|
";alert('XSS Attack!');aaa="message |
上記例のように、ユーザーの入力を導出元としてコードを出力するなど、JavaScriptの要素を動的に生成する場合、意図せず文字列リテラルが閉じられ、XSSの脆弱性が生じる。
出力結果
<script type="text/javascript">
var aaa = '';alert('XSS Attack!');aaa='message';
alert(aaa);
</script>
出力結果
<script type="text/javascript">
var aaa = "";alert('XSS Attack!');aaa="message";
alert(aaa);
</script>
Tip
業務要件上必要でない限り、JavaScriptの要素をユーザーからの入力値に依存して動的に生成する仕様は、任意のスクリプトが埋め込まれてしまう可能性があるため、別の方式を検討する、または、極力避けるべきである。
9.7.1.3.2. 出力値をエスケープする例¶
XSSを防ぐために、JSPではユーザーの入力値が設定される値にEL式の関数、f:js()
の使用を推奨する。
また、Thymeleafではth:inline="javascript"
の使用を推奨する。詳細は、Tutorial: Using Thymeleaf -JavaScript inlining-を参照されたい。
使用例を、下記に示す。
<script type="text/javascript">
var aaa = '${f:js(warnCode)}'; <!-- (1) -->
alert(aaa);
</script>
項番 |
説明 |
---|---|
(1)
|
EL式の
f:js() を使用することにより、エスケープして変数に設定している。 |
出力結果
<script type="text/javascript">
var aaa = '\';alert(\'XSS Attack!\');aaa=\'message';
alert(aaa);
</script>
Warning
スクリプトタグが含まれる値を、HTMLエスケープせずf:js()
でエスケープさせて出力する場合、document.write()を使用すると、
ブラウザにHTMLソースとして解釈させるよう出力するので、XSSの脆弱性が生じる。以下に例を示すが、このような実装は決して行わないこと。
JSP
<script type="text/javascript"> var aaa = '${f:js(warnCode)}'; document.write(aaa); </script>
属性名
値
warnCode<script>alert('XSS Attack!');</script>
出力結果
<script type="text/javascript"> var aaa = '\x3cscript\x3ealert(\'XSS Attack!\');\x3c\/script\x3e'; document.write(aaa); </script>
出力結果をソースだけ確認するとエスケープできているように見える。しかし、これは<script>alert('XSS Attack!');</script>
という内容の文字列を変数aaaに格納するコードとなるため、document.write(aaa);
と実装してしまうと、HTMLのソースとして<script>alert('XSS Attack!');</script>
を出力することになる。その結果、スクリプトが実行される。
ブラウザに値を出力させたい場合は、JavaScriptを使用せず、HTML特殊文字をエスケープするf:h()
を使用することが望ましい。
JSP
${f:h(warnCode)}
出力結果
<script>alert('XSS Attack!');</script>
あえてf:js()
を使用し、document.write()で出力したい場合は、以下のいずれかのような、追加のXSS対策が必要である。
HTMLエスケープ用のJavaScript関数を用意し、document.write()の引数をエスケープする。
f:h()
でユーザーの入力値が設定される値をHTMLエスケープした後、f:js()
でJavaScriptの文字列リテラル用のエスケープを行う。
<script type="text/javascript" th:inline="javascript"> <!--/* (1) */-->
var aaa = [[${warnCode}]];
alert(aaa);
</script>
項番 |
説明 |
---|---|
(1)
|
th:inline="javascript" と[[xxx]] の形式を用いたインライン記法を併用することにより、エスケープして変数に設定している。 |
出力結果
<script type="text/javascript">
var aaa = "\";alert('XSS Attack!');aaa=\"message";
alert(aaa);
</script>
Note
th:inline="javascript"
と[[xxx]]
の形式を用いたインライン記法を併用すると、文字列が”"
“に挟まれた状態で出力されるので、”'
“はエスケープ不要となる。
また、<script>タグがブラウザに認識されると、</script>のようにタグを閉じるまで他のタグは認識されない。そのため、”/
“がエスケープされていれば、”<
“、”>
“のエスケープは不要となる。
以上のことから、以下の特殊文字はth:inline="javascript"
のエスケープ対象に入っていない。
“
'
““
<
““
>
“
インライン記法については、JavaScriptテンプレートの適用のインライン記法の項を参照されたい。
Warning
スクリプトタグが含まれる値を、HTMLエスケープせずth:inline="javascript"
でエスケープさせて出力する場合、document.write()を使用すると、
ブラウザにHTMLソースとして解釈させるよう出力するので、XSSの脆弱性が生じる。以下に例を示すが、このような実装は決して行わないこと。
HTML
<script type="text/javascript" th:inline="javascript"> var aaa = [[${warnCode}]]; document.write(aaa); </script>
属性名
値
warnCode<script>alert('XSS Attack!');</script>
出力結果
<script type="text/javascript"> var aaa = "<script>alert('XSS Attack!');<\/script>"; document.write(aaa); </script>
出力結果をソースだけ確認するとエスケープできているように見える。しかし、これは<script>alert('XSS Attack!');</script>
という内容の文字列を変数aaaに格納するコードとなるため、document.write(aaa);
と実装してしまうと、HTMLのソースとして<script>alert('XSS Attack!');</script>
を出力することになる。その結果、スクリプトが実行される。
ブラウザに値を出力させたい場合は、JavaScriptを使用せず、HTML特殊文字をエスケープするth:text
属性を使用することが望ましい。
HTML
<div th:text=”${warnCode}”>warn code</div>
出力結果
<div><script>alert('XSS Attack!');</script></div>
あえてdocument.write()で出力したい場合は、以下のいずれかのような、追加のXSS対策が必要である。
HTMLエスケープ用のJavaScript関数を用意し、document.write()の引数をエスケープする。
th:text
属性でユーザーの入力値が設定される値をHTMLエスケープした後、th:inline="javascript"
でJavaScriptの文字列リテラル用のエスケープを行う。
9.7.1.4. Event handler Escaping¶
javascript のイベントハンドラの値をエスケープする場合、f:h()
や、f:js()
を使用するのではなく、f:hjs()
を使用すること。${f:h(f:js())}
と同義である。
理由としては、 <input type="submit" onclick="callback('xxxx');">
のようなイベントハンドラの値に');alert("XSS Attack");//
を指定された場合、別のスクリプトを挿入できてしまうため、文字参照形式にエスケープ後、HTMLエスケープを行う必要がある。
[[xxx]]
の形式を用いたインライン記法を使用する。理由としては、 <input type="submit" onclick="callback('xxxx');">
のようなイベントハンドラの値に');alert("XSS Attack");//
を指定された場合、別のスクリプトを挿入できてしまうため、文字参照形式にエスケープ後、HTMLエスケープを行う必要がある。
Note
Thymeleaf 3.0.10より、イベントハンドラの値をインライン記法で記述できるように変更された。インライン記法は自動的にJavaScriptテンプレートモードで解釈される。
インライン記法については、JavaScriptテンプレートの適用のインライン記法の項を参照されたい。
Warning
Thymeleaf 3.0.10より、イベントハンドラの値を従来のインライン記法以外で記述する場合、Booleanと数値以外を出力する式がエラーとなるように変更された。
これは、従来の記法では式により出力される文字列が区切り文字(シングルクォートやダブルクォート)で囲まれないため、JavaScript構文の出力により脆弱性を埋め込むことが容易だったためである。
従来の記法では大幅に機能が制限されるため、イベントハンドラの値はインライン記法で記述することを推奨する。
9.7.1.4.1. 出力値をエスケープしない脆弱性のある例¶
XSS問題が発生する例を、以下に示す。
<input type="text" onmouseover="alert('output is ${warnCode}') . ">
属性名 |
値 |
---|---|
warnCode
|
'); alert('XSS Attack!'); // 上記の値が設定されてしまうことで、意図せず文字列リテラルが閉じられ、XSSの脆弱性が生じる。
|
<input type="text" th:onmouseover="alert("[(|output is ${warnCode}.|)]")">
属性名 |
値 |
---|---|
warnCode
|
\ "); alert('XSS Attack!'); // 上記の値が設定されてしまうことで、意図せず文字列リテラルが閉じられ、XSSの脆弱性が生じる。
|
マウスオーバ時、XSSのダイアログボックスが表示されてしまう。
出力結果
<!-- omitted -->
<input type="text" onmouseover="alert('output is'); alert('XSS Attack!'); // .') ">
<!-- omitted -->
出力結果
<!-- omitted -->
<input type="text" onmouseover="alert("output is "); alert('XSS Attack!'); //.")">
<!-- omitted -->
Note
[[xxx]]
の形式を用いたインライン記法を使用すると、エスケープされた文字列がダブルクォート(”"
“)で囲まれて出力される。
これに合わせて、本ガイドラインではエスケープしない場合でも文字列をダブルクォート("
)で囲んでいる。もちろんシングルクォートで囲んでも問題ない。
9.7.1.4.2. 出力値をエスケープする例¶
使用例を、下記に示す。
<input type="text" onmouseover="alert('output is ${f:hjs(warnCode)}') . "> <!-- (1) -->
項番 |
説明 |
---|---|
(1)
|
EL式の
f:hjs() を使用することにより、エスケープして引数としている。 |
<input type="text" th:onmouseover="alert([[|output is ${warnCode}.|]])"> <!--/* (1) */-->
項番 |
説明 |
---|---|
(1)
|
Thymeleafの
[[xxx]] の形式を用いたインライン記法を使用することにより、エスケープしている。 |
マウスオーバ時、XSSのダイアログは出力されない。
出力結果
<!-- omitted -->
<input type="text" onmouseover="alert('output is \'); alert(\'XSS Attack!\');\" \/\/ .') ">
<!-- omitted -->
出力結果
<!-- omitted -->
<input type="text" onmouseover="alert("output is \"); alert('XSS Attack!'); \/\/.")">
<!-- omitted -->