MENU

script要素内で発生するクロスサイト・スクリプティング(XSS)とは?原因と対策を解説【セキュア開発技術Blog】

Securityチーム Web脆弱性診断員の深井です。

今回は、ここ1年以内でよく見かけるようになってきたscript要素にユーザーの入力値が反映されてしまった際に発生するクロスサイト・スクリプティングについて解説していきます。

目次

クロスサイト・スクリプティング(XSS)とは

文字エンティティ用途
&&エンティティの開始文字
<&lt;タグ開始文字
>&gt;タグ終了文字
"&quot;ダブルクォーテーション(属性値)
'&#39; または &apos;*シングルクォーテーション(属性値)

通常はHTMLエスケープにより対策が可能

Webアプリケーションでは、ユーザーが入力した値を画面へ表示することがよくあります。例えば、ECサイトで商品購入時に住所や名前を入力し、入力された内容を確認画面にて出力するというのがイメージしやすいと思います。

多くの場合、入力値はHTML本文やHTML属性値へ出力されるため、HTMLエンティティエンコードを行うことで、ブラウザに文字列として扱わせることができます。

HTML本文へ出力する場合

例えば、ユーザー名を画面へ表示する場合です。

<?php
$name = $_GET['name'];
?>

<p><?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?></p>

この時、悪意あるユーザーが次のような値を入力したとします。

<script>alert(1)</script>

しかし、上記の通り、htmlspecialchars() が適用されているため、ブラウザへは次のようなHTMLが出力されます。

<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>

scriptタグはHTMLタグとしてではなく文字列として扱われるため、スクリプトは実行されません。

HTML属性値へ出力する場合

続いてHTML属性値に反映される場合の例を見ていきます。

<?php
$keyword = $_GET['keyword'];
?>

<input
    type="text"
    value="<?= htmlspecialchars($keyword, ENT_QUOTES, 'UTF-8') ?>"
>

この時、悪意あるユーザーが次のような値を入力したとします。

" onfocus="alert(1)

これは、value属性の値を最初の"で閉じた上で、イベントハンドラ(onfocus)を追加しており、以下のような形になります。

<input type="text" value="" onfocus="alert(1)"

しかし、同じようにhtmlspecialchars() が適用されているため、ブラウザへは次のようなHTMLが出力されます。

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

ダブルクォーテーションがHTMLエスケープされ、value 属性の途中で終了することはありません。

その結果、onfocus 属性は追加されず、入力値は単なる文字列として扱われます。

このように、HTML属性へ外部入力を出力する場合は、htmlspecialchars() などによるHTMLエスケープを行うことで、多くのXSSを防ぐことができます。

「HTMLエスケープしているから安全」は本当か?

XSSの対策にはスクリプトなどの外部入力値をHTMLエスケープすることが一般的ですが、script要素内にユーザーの入力値が反映されるケースでは、HTMLエスケープだけでは対処しきれません。

script要素内で発生するXSSの具体例

まずは例として以下のプログラムを見ていきます。

<script>
var name = "<?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>";
</script>

しかし、<script> 要素内で生成されるのはHTMLではなくJavaScriptのコードです。そのため、このコンテキストではHTMLエスケープではなく、JavaScript文字列リテラルとしてのエスケープが必要になります。

例えば、JavaScript文字列リテラルでは引用符("')、バックスラッシュ(\)、改行などを適切にエスケープしなければ、文字列を途中で終了させ、任意のJavaScriptを実行される可能性があります。

"; alert(1); //
var name = "";
alert(1);
//";

script要素内でのXSS対策

対策1. 外部入力を script 要素内に直接反映しない

最も有効な対策は、ユーザー入力やリクエストパラメータなどの外部入力を、そのまま script 要素内に埋め込まないことです。
htmlspecialchars() を使用していても、script要素内では十分な対策とはいえません。

また、外部入力を画面へ反映する必要がないのであれば、そもそも出力しないことが最も安全です。

さらに、名前やID、カテゴリなど、どのような入力値なのか形式が決まっているのであれば、許可リストや入力値の検証(例:正規表現)を行い、想定外の値を受け付けないようにすることも対策の1つとなります。

対策2. data-* 属性を利用する

外部入力をJavaScriptで利用する必要がある場合は、script要素内へ直接埋め込まず、HTMLの data-*属性に保持し、JavaScriptから取得する方法を推奨します(以下は一例です)。

HTML側では、外部入力をHTMLエンティティエンコードしたうえで data-*属性へ格納します。

<div
    id="user"
    data-name="<?= htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>"
></div>

<script src="app.js"></script>

例えば、ユーザーが入力した値が </script><script>alert(1)</script> だった場合、HTMLには次のように出力されます。

<div
    id="user"
    data-name="&lt;/script&gt;&lt;script&gt;alert(1)&lt;/script&gt;"
></div>

<script src="app.js"></script>

この方法では、外部入力がscript要素内へ埋め込まれないため、</script> によってscript要素が終了する問題を回避できます。

JavaScriptではdatasetを利用してdata-*属性の値を取得します。

window.onload = function () {
    var div = document.getElementById("user");
    var name = div.dataset.name;

    init(name);
};

このように、HTMLからデータを取得することで、JavaScriptコード内へ外部入力を直接埋め込む必要がなくなります。

このようにすることで、HTMLから安全に値を受け渡すことができます。

data-* 属性を利用する際の注意点

対策2では、外部入力をdata-*属性へ格納し、JavaScriptから取得する方法を紹介しました。しかし、この対策を行っても、値を反映する際にinnerHTMLを使用してしまうと再びブラウザによるHTMLとしての解釈が行われ、悪意あるスクリプトが実行される可能性が残ります。

そのため、画面へ文字列として表示したい場合は、innerHTMLではなくtextContentを使用してください。

textContent は文字列をHTMLとして解釈せず、そのままテキストとして表示するため、安全に扱うことができます。

まとめ

script要素内に外部入力を直接埋め込むと、htmlspecialchars()などでHTMLエンティティエンコードを行っていても、</script>によってscript要素が終了し、XSSが発生する可能性があります。

これは、HTMLパーサがJavaScriptの文法を理解しているわけではなく、</script>をscript要素の終了タグとして解釈するためです。

そのため、外部入力をJavaScriptで利用する場合は、次の点を意識しましょう。

  • 外部入力を script 要素内へ直接埋め込まない。
  • 外部入力をJavaScriptへ渡す必要がある場合は、HTMLエンティティエンコードを行ったうえで data-* 属性などへ保持し、JavaScriptから取得する。
  • data-* 属性から取得した値は、HTMLエンティティがデコードされた文字列として取得されるため、そのまま innerHTML へ渡さない。
  • 画面へ表示する場合は、innerHTML ではなく textContent など、文字列をテキストとして扱うAPIを利用する。

XSS対策では、「どのように値を渡すか」だけでなく、「取得した値をどのように利用するか」まで含めて考えることが重要です。出力先のコンテキストに応じた適切な処理を行い、安全なWebアプリケーションを実装しましょう。

セキュアなシステム開発についての
\ 無料相談受付中! /

この記事を書いた人
深井 亨

株式会社神戸デジタル・ラボ
デジタルビジネス本部 Securityチーム

2019年に新卒でSIer企業へ入社し、自社プロダクトの開発業務に従事。
2023年4月に神戸デジタル・ラボへ入社し、主にWebアプリケーション脆弱性診断、クラウドセキュリティ設定診断を担当。またPSIRT業務(脆弱性のトリアージ)など診断以外にも携わる。
Udemy講座「脆弱性診断員が教えるAWSクラウドセキュリティ設定トレーニング」の講師を担当。

【参考】
体系的に学ぶ 安全なWebアプリケーションの作り方
https://www.sbcr.jp/product/4797361193

  • URLをコピーしました!
目次