はじめに
こんにちは。
プログラミング初心者wakinozaと申します。
勉強中に調べたことを記事にまとめています。
十分気をつけて執筆していますが、なにぶん初心者が書いた記事なので、理解が浅い点などあるかと思います。
間違い等あれば、指摘いただけると助かります。
記事を参考にされる方は、初心者の記事であることを念頭において、お読みいただけると幸いです。
記事のテーマ
- Webアプリケーションのセキュリティについて勉強しています。
- この記事では、Java(Servlet/JSP)での、クロスサイト・リクエスト・フォージェリ(CSRF)対策についてまとめます。
目次
1. クロスサイト・リクエスト・フォージェリ(CSRF)
2. CSRF脆弱性
3. 対策方法
4. 補助的な対策方法
1. クロスサイト・リクエスト・フォージェリ(CSRF)
クロスサイト・リクエスト・フォージェリ(CSRF)とは、ログイン中の標的ユーザのブラウザに意図しないリクエストを送らせる攻撃手法です。
多くのWebアプリケーションでは、ログイン後に様々なサービスを受けることができます。
それは、内部で「ユーザの認証は完了している」という情報がブラウザやサーバに保存されているからです。
攻撃者は、すでに認証が完了しているブラウザをターゲットに、保存された認証状態を悪用して、ユーザの意図しない処理を強要する手段を思いつきました。
それが、CSRFです。
CSRFの具体的な流れを見ていきましょう。
まず攻撃者は、標的ユーザのブラウザを操作する攻撃用スクリプトを、罠サイトに仕込みます。
以下は、攻撃用スクリプトが埋め込まれた罠サイトのコード例です。
<html>
<body onload="document.forms[0].submit()">
<p>ページを読み込んでいます。少々お待ちください...</p>
<form action="http://localhost:8080/targetApp/add" method="POST">
<input type="hidden" name="attackTask" value="知らない間にタスクが追加されました">
</form>
</body>
</html>
「onload="document.forms[0].submit()"」が記載されているため、罠サイトを開いた瞬間にformからリクエストが送信されます。
罠サイトのformには、action属性にターゲットとなるアプリケーションのURLが指定され、POSTリクエストで、hiddenパラメータの情報が送信されるようになっています。
標的ユーザがこのような罠サイトを閲覧してしまうと、即座に標的ユーザのブラウザ上で攻撃スクリプトが実行され、アプリケーションサーバにPOSTリクエストが送信されます。
サーバは、ログイン認証が済んでいるブラウザからのリクエストなので、認証なしでリクエストを受理してしまいます。その結果、標的ユーザの知らぬ間に、攻撃者の意図した処理がサーバ上で実行されてしまうのです。
CSRFで発生しうる脅威は、以下の通りです。
- ユーザのみが利用可能なサービスの悪用:送金、物品購入、退会処理など。
- ユーザのみが編集可能な情報の改ざん:メールアドレス・パスワードの変更、なりすまし投稿など。
ちなみに、クロスサイト・リクエスト・フォージェリ(CSRF)とよく似た名前の攻撃手法として、クロスサイト・スクリプティング(XSS)があります。
この2つは名前が似ているだけでなく、「不正なスクリプトによって標的ユーザのブラウザを操作する」攻撃手法もとても似ています。
2つの違いは、攻撃の最終目的です。
CSRFは、認証済みブラウザを踏み台にしてサーバ側の状態を不正に変更する攻撃です。
一方XSSは、ユーザのブラウザ内で悪意のあるコードを実行し、セッション窃取やフィッシングなどを可能にする攻撃です。
言い換えると、CSRFはサーバを騙す攻撃、XSSはユーザを騙す攻撃となります。
2. CSRF脆弱性
CSRF脆弱性が生まれる背景には、以下のWeb機能があります。
- form要素のaction属性にはどのドメインのURLでも指定できる
- Cookieに保管されたセッションIDは、対象サイトに自動的に送信される。
Webには、form要素のaction属性に別のドメインのURLを指定できる性質(クロスサイト・リクエスト)があります。
クロスサイト・リクエストは、Webサービスの利便性と拡張性を支える重要な性質です。
別のドメインに自由にリクエストを送れるこの仕組みがあるおかげで、サービス提供者は外部サービスを手軽に利用できるようになります。決済は外部の決済サービスを、アンケートは外部のアンケートサービスを利用するという具合です。
もし、他ドメインへのリクエストが禁止されていたら、外部サービスの利用は今よりも難しくなっていたでしょう。クロスサイト・リクエストという性質のおかげで、Webサービスの利便性と拡張性は大幅に向上したのです。
また、Webサービスを閲覧するブラウザにも、クロスサイト・リクエストに対応したシステムが構築されています。
例えば、ブラウザが送信先ドメインのCookie(特にセッションIDを保持するもの)を所持している場合、リクエスト送信時にそれらを「自動的」に付与します。
この時、ブラウザは「リクエストの発信源」を確認しません。それは、クロスサイト・リクエストによって、他ドメインからのリクエストが想定されるためです。
この「クロスサイト・リクエスト」と「Cookieの自動送信」という便利な仕組みを悪用したのが、CSRFです。
他ドメインからのリクエストが許可されていることで、「リクエストの偽造」が、容易になってしまったのです。
とはいえ、リクエストが偽造できても、パスワードの偽造はできないため、認証は突破できません。
しかし、すでに認証が完了し、Cookieを保存しているブラウザは、偽造されたリクエストにも自動的にCookieを付与してしまいます。
そのため、ログイン中のブラウザを不正なスクリプトによって操作することで、攻撃者の意図した処理をサーバ上で実行させます。
3. 対策方法
次に、具体的なCSRF対策方法を見ていきます。
まず前提として、CSRF対策が必要なページとそうでないページを区別することから始めます。
現代のWebサービスは、検索エンジンやSNS・リンクなど様々な外部サービスをリンクされているページが多くあります。その中には、商品カタログのページのように、外部からリンクが望ましいページもあります。
望ましいページにまでCSRF対策を実施すると、Webアプリケーションへのユーザの流入が妨げられてしまいます。
一方で、ECサイトの購入処理画面やパスワードの変更画面などは、他のサイトからのリクエストを許すと、CSRFのリスクが上がります。
そのため、要件定義や基本設計の工程のなかで、CSRF対策が必要なページを絞り込んでおくことが重要です。
次に、CSRF対策が必要なページに対して、具体的にどのような対策が必要なのかをJava(Servlet/JSP)を例に紹介していきます。
CSRF対策として必要なのは、正規ユーザのリクエストであることを確認することです。
正規ユーザからのリクエストであることを判別する方法として、「トークンの埋め込み」「パスワードの再入力」があります。
3-1. トークンの埋め込みと同一オリジンポリシー
CSRF対策の1つが、「トークン」と「同一オリジンポリシー」のセットです。
トークンとは、「秘密の合言葉」のようなもので、ログイン認証時にサーバで生成され、ブラウザに保管され、リクエストの認証のためにも用いられます。
重要な変更を行うリクエストを送信する際に、hidden属性などの隠しフィールドにこのトークンを付与します。
すると、サーバは、リクエスト内のトークンとサーバに保存されたトークンを比較することで、受信したリクエストが正規のものかどうかを検証することができます。
Cookieとトークンは、どちらも正規のリクエストであることを証明するための「秘密の合言葉」です。
2つの違いは、Cookieは同じドメインへのリクエストに対してブラウザが自動的に付加してしまうのに対し、トークンは開発者が記述しなければリクエストに含まれないという点です。
このため、もし標的ユーザが罠サイトを閲覧して、攻撃スクリプトによってPOSTリクエストが送信されても、攻撃スクリプトは正規のトークンを含めることができません。サーバはリクエスト内のトークン情報を確認することで、正規のリクエストである可能性を高く検証できます。
では、罠サイトにトークンを付与するコードが記載されていればどうでしょう?
<html>
<body onload="document.forms[0].submit()">
<p>ページを読み込んでいます。少々お待ちください...</p>
<form action="http://localhost:8080/targetApp/add" method="POST">
<input type="hidden" name="attackTask" value="知らない間にタスクが追加されました">
<!-- トークンを付与するコード -->
<input type="hidden" name="csrfToken" value="???">
</form>
</body>
</html>
トークンを格納した変数名は、開発者ツールでHTMLを確認すれば簡単に判明します。
攻撃者がトークンの変数名を特定し、悪意のあるスクリプト内にその変数を含めた場合、トークンはリクエストに付与されるのでしょうか?
ここで重要になるのが、「同一オリジンポリシー(SOP)」です。
SOPは、異なるオリジン間でスクリプトによるレスポンスデータの読み取りを制限する仕組みです。具体的には、「スキーム(httpかhttps)」「ホスト( example.com)」「ポート(80など)」がすべて一致する同一オリジンにのみ、データの読み取りを許可する設定です。
このため、別ドメインである罠サイト上のスクリプトは、正規サイトが発行したトークンをHTMLから読み取ることができません。
CookieはSOPとは別のCookieポリシーで管理されており、対象ドメインへのリクエストには自動的に付与されます。
しかし、トークンが埋め込まれたページの内容はSOPによって保護されるため、別ドメインのスクリプトはトークンを読み取ることができません。
そのため、攻撃者は正規のトークンを知ることができず、リクエストに含めることが不可能になります。
SOPは、現代のブラウザの標準仕様であるため、開発者が明示的にコードを記述する必要はありません。
ちなみに、トークンは推測困難な乱数で生成されるべきであり、推測することは困難です。
また、セッション単位やフォーム単位で新しいトークンを生成し、ログアウトやセッションタイムアウトでセッションが破棄されると、自動的にトークンも破棄されます。
このように使い捨てのワンタイムトークンを利用することで、CSRFを防ぐことができるのです。
ちなみに、もし攻撃者がトークンを盗もうとした場合は、XSS攻撃を仕掛けてブラウザから情報を引き出そうとします。
そのため、XSS脆弱性が存在すると、CSRFトークンが盗まれ、CSRF攻撃の成功率が上がってしまいます。
そういった理由から、CSRF対策とXSS対策は常にセットで考える必要があります。
では、Servlet/JSPでの実装例を見ていきます。
まずは、ログイン時や、重要な操作をする画面を表示するタイミングでトークンを生成します。
以下のコードでは、java.security.SecureRandomクラスを用いてトークンを生成します。
SecureRandomクラスは、セキュリティの堅い乱数を生成するクラスです。
import java.security.SecureRandom;
import java.util.Base64;
public class CsrfTokenGenerator {
private static final SecureRandom secureRandom = new SecureRandom();
private static final Base64.Encoder base64Encoder =
Base64.getUrlEncoder().withoutPadding();
public static String generateToken() {
byte[] randomBytes = new byte[32]; // 256bit
secureRandom.nextBytes(randomBytes);
return base64Encoder.encodeToString(randomBytes);
}
}
// ユーザー認証成功後、新しいセッションを作成
HttpSession session = request.getSession(true);
// トークン生成
String csrfToken = CsrfTokenGenerator.generateToken();
// セッションに保存
session.setAttribute("csrfToken", csrfToken);
次に、EL式を使って、セッション内のトークンを取り出し、hiddenフィールドにセットします。
副作用を伴う処理では、必ずPOSTメソッドを利用します。
副作用を伴う処理にGETメソッドを利用すると、トークンがURLパラメータに含まれることになります。これは、Refererヘッダを介した外部への漏洩や、ブラウザ履歴・サーバーログへの意図しない残存を招くリスクがあります。
<form action="doTask" method="POST">
<input type="text" name="task">
<input type="hidden" name="csrfToken" value="${sessionScope.csrfToken}">
<button type="submit">追加</button>
</form>
//サーバで保管されたトークン
String savedToken = (String)session.getAttribute("csrfToken");
// リクエストで送信されたトークン
String requestToken = request.getParameter("csrfToken");
if (savedToken != null && savedToken.equals(requestToken)) {
// トークンは一致
} else {
// トークンが一致しない。攻撃の疑いあり!
}
3-2. パスワードの再入力
2つ目の方法は、パスワードの再入力です。
仕組みは簡単で、重要な処理を実行する際に、再度パスワードの入力を要求するという仕組みです。
攻撃者は、ブラウザのCookie自動添付という仕組みを利用しているだけで、攻撃が通る前は、標的ユーザのCookie情報もパスワード情報も保持していません。そのため、リクエストを送っても、パスワードがわからないため、重要な処理を実行することができなくなります。
この仕組みは、シンプルではありますが、ユーザーにパスワード入力を複数回要求するため、ユーザ体験を下げるリスクもあります。そのため、UXとセキュリティのバランスを考えて実装する必要があります。
4. 補助的な対策方法
4-1. メールの自動送信
補助的な対策方法としては、重要な操作を行った際に、その旨を登録済みメールアドレスに送信するというものがあります。
メールの通知は事後報告になるため、CSRF攻撃そのものを防ぐ効果はありません。
しかし、CSRFは標的ユーザが気づかぬうちに実行されている場合が多いため、早期の連絡によって、利用者が異変に気付くきっかけを作ることができます。
4-2. SameSite属性
現在のブラウザでは、別サイトからのリクエストにCookieを付与しない設定が可能です。
ブラウザのSameSite属性をLaxとすることで、外部サイトからのPOSTリクエストに対してCookieの送信を制限します。
ただし、SameSite属性によるCookie付与の制限には、例外があります。
SameSite属性の最大の例外は、ユーザーがリンクをクリックしてGETリクエストを送信する場合は、Cookieが送信されてしまうという点です。
もし、アプリケーションが重要な操作を「GET」リクエストで受け付ける設計にしていた場合、aタグによるリンク遷移と組み合わせることで、Lax導入下でもCSRFが通ってしまう可能性があります。
そのため、SameSite属性は強力な対策ですが、単体では完全なCSRF対策にはなりません。
サーバ側のトークン検証などの対策をメインに設定し、SameSite属性はあくまで補助的な対策と考える必要があります。
多くのブラウザでは、SameSite属性はLaxがデフォルト値となっています。
しかし、将来的なブラウザの仕様変更や古いバージョンへの対応の観点から、サーバー側で明示的に指定しておくと、より安全です。
4-3. Refererヘッダのチェック
HTTPリクエストには「Refererヘッダ」という、「そのリクエストを送る直前に、ユーザがそのページにいたのか」を記載する項目があります。
このRefererヘッダをチェックすることで、そのリクエストがアプリケーションのURLから送られた正規のリクエストなのか、罠サイトから送られた不正なリクエストなのかを判別することができます。
しかし、通常のWebアプリケーションでは、このRefererヘッダをチェックしません。
その理由は、一部のセキュリティソフトやブラウザでは、セキュリティ対策として、Refererヘッダを表示しない設定が可能だからです。
もし、Refererヘッダをチェックする体制を実施すると、攻撃者はそれに合わせてRefererヘッダをリクエストに表示しない設定に変更するでしょう。もしサーバー側でRefererヘッダのチェックを必須とした場合、プライバシー保護の観点からヘッダを送信しない設定にしている正規ユーザーのリクエストまで拒否してしまうことになります。
Refererヘッダのチェックは有効な手段ですが、漏れが生じやすいという欠点もあります。
まとめ
-
トークンによる検証: SOP(同一オリジンポリシー)によって「別ドメインからはトークンを読み取れない」というブラウザの制約を利用し、リクエストの正当性をサーバー側で厳密に検証する。
-
適切なHTTPメソッドの選択: SameSite=Lax 下でもリンク遷移(GET)ではCookieが送信されるため、副作用を伴う処理には必ずPOST(またはそれ以上のメソッド)を採用する。
記事は以上です。
最後までお読みいただき、ありがとうございました。
参考情報一覧
この記事は以下の情報を参考にして執筆しました。
- [体系的に学ぶ 安全なWebアプリケーションの作り方 第2版]
- [スッキリわかるサーブレット&JSP入門 第4版]
- [基礎からのサーブレット/JSP 第5版]
- 安全なウェブサイトの作り方 - 1.6 CSRF(クロスサイト・リクエスト・フォージェリ) (参照 2026-02-03)
- CSRFに関して(最終更新 2018-07-25) (参照 2026-02-03)
- CSRF トークンとは?(最終更新 2025-03-12) (参照 2026-02-03)
- 同一オリジンポリシーを理解する(最終更新 2020-12-13) (参照 2026-02-03)
- JavaのSecureRandomクラスの使い方を完全ガイド!初心者でもわかるセキュアな乱数生成(最終更新 2026-01-26) (参照 2026-02-03)