9.5. CSRF対策


9.5.1. Overview

本節では、Spring Securityが提供しているCross site request forgeries(以下、CSRFと略す)対策の機能について説明する。

CSRFとは、Webサイトにスクリプトや自動転送(HTTPリダイレクト)を実装することにより、ログイン済みの別のWebサイト上で、ユーザーが意図しない何らかの操作を行わせる攻撃手法のことである。

サーバ側でCSRFを防ぐには、以下の方法が知られている。

  • 秘密情報(トークン)の埋め込み

  • パスワードの再入力

  • Refererのチェック

CSRF対策機能は、攻撃者が用意したWebページから送られてくる偽造リクエストを不正なリクエストとして扱うための機能である。
CSRF対策が行われていないWebアプリケーションを利用すると、以下のような方法で攻撃を受ける可能性がある。
  • 利用者は、CSRF対策が行われていないWebアプリケーションにログインする。

  • 利用者は、攻撃者からの巧みな誘導によって、攻撃者が用意したWebページを開いてしまう。

  • 攻撃者が用意したWebページは、フォームの自動送信などのテクニックを使用して、偽造したリクエストをCSRF対策が行われていないWebアプリケーションに対して送信する。

  • CSRF対策が行われていないWebアプリケーションは、攻撃者が偽造したリクエストを正規のリクエストとして処理してしまう。

Tip

OWASP[1]では、トークンパターンを使用する方法が推奨されている。

Note

ログイン時におけるCSRF対策

CSRF対策はログイン中のリクエストだけではなく、ログイン処理でも行う必要がある。

ログイン処理に対してCSRF対策を怠った場合、攻撃者が用意したアカウントを使って知らぬ間にログインさせられ、ログイン中に行った操作履歴などを盗まれる可能性がある。

Warning

マルチパートリクエスト(ファイルアップロード)時におけるCSRF対策

ファイルアップロード時のCSRF対策については、ファイルアップロード Servlet Filterの設定を留意されたい。


9.5.1.1. Spring SecurityのCSRF対策

Spring Securityは、セッション単位にランダムに生成される固定トークン値(CSRFトークン)を払い出し、払い出されたCSRFトークンをリクエストパラメータ(HTMLフォームのhidden項目)として送信する。
これにより正規のWebページからのリクエストなのか、攻撃者が用意したWebページからのリクエストなのかを判断する仕組みを採用している。
../_images/Csrf.png

Spring SecurityのCSRF対策の仕組み

項番

説明

(1)
クライアントは、HTTPのGETメソッドを使用してアプリケーションサーバにアクセスする。
(2)
Spring Securityは、CSRFトークンを生成しHTTPセッションに格納する。
生成したCSRFトークンは、HTMLフォームのhiddenタグを使ってクライアントと連携する。
(3)
クライアントは、HTMLフォーム内のボタンを押下してアプリケーションサーバーにリクエストを送信する。
HTMLフォーム内のhidden項目にCSRFトークンが埋め込まれているため、CSRFトークン値はリクエストパラメータとして送信される。
(4)
Spring Securityは、HTTPのPOSTメソッドを使ってアクセスされた際は、リクエストパラメータに指定されたCSRFトークン値とHTTPセッション内に保持しているCSRFトークン値が同じ値であることをチェックする。
トークン値が一致しない場合は、不正なリクエスト(攻撃者からのリクエスト)としてエラーを発生させる。
(5)
クライアントは、HTTPのGETメソッドを使用してアプリケーションサーバにアクセスする。
(6)
Spring Securityは、GETメソッドを使ってアクセスされた際は、CSRFトークン値のチェックは行わない。

Note

Ajax使用時のCSRFトークン

Spring Securityは、リクエストヘッダにCSRFトークン値を設定することができるため、Ajax向けのリクエストなどに対してCSRF対策を行うことが可能である。


9.5.1.1.1. トークンチェックの対象リクエスト

Spring Securityのデフォルト実装では、以下のHTTPメソッドを使用したリクエストに対して、CSRFトークンチェックを行う。

  • POST

  • PUT

  • DELETE

  • PATCH

Note

CSRFトークンチェックを行わない理由

GET, HEAD, OPTIONS, TRACE メソッドがチェック対象外となっている理由は、これらのメソッドがアプリケーションの状態を変更するようなリクエストを実行するためのメソッドではないためである。


9.5.2. How to use

9.5.2.1. CSRF対策機能の適用

CSRFトークン用のRequestDataValueProcessor実装クラスを利用し、JSPではSpringのタグライブラリの<form:form>タグを、Thymeleafではth:action属性を使うことで、自動的にCSRFトークンをhidden項目に埋め込むことができる。

  • SpringMvcConfig.javaの設定例

@Bean("requestDataValueProcessor")
public RequestDataValueProcessor requestDataValueProcessor() {
    return new CompositeRequestDataValueProcessor(csrfRequestDataValueProcessor(), transactionTokenRequestDataValueProcessor()); // (1)
}

@Bean
public CsrfRequestDataValueProcessor csrfRequestDataValueProcessor() {
    return new CsrfRequestDataValueProcessor(); // (2)
}

@Bean
public TransactionTokenRequestDataValueProcessor transactionTokenRequestDataValueProcessor() {
    return new TransactionTokenRequestDataValueProcessor();
}

項番

説明

(1)
共通ライブラリから提供されている、org.springframework.web.servlet.support.RequestDataValueProcessorを複数定義可能な
org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessorをbean定義する。
(2)
コンストラクタの第1引数に、org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessorのbean定義を設定する。

上記設定により、デフォルトでCSRF対策機能が有効となる。このため、CSRF対策機能を適用したくない場合は、明示的に無効化する必要がある。

CSRF対策機能を使用しない場合は、以下のようなbean定義を行う。

  • SpringSecurityConfig.javaの定義例

@Bean
public SecurityFilterChain filterChainJspDisabledcspr(HttpSecurity http) {
    // omitted
    http.csrf(csrf -> csrf.disable());
    // omitted
    return http.build();
}

9.5.2.2. CSRFトークン値の連携

Spring Securityは、CSRFトークン値をクライアントとサーバー間で連携する方法として、以下の2種類の方法を提供している。

  • HTMLフォームのhidden項目としてCSRFトークン値を出力し、リクエストパラメータとして連携する

  • HTMLのmetaタグとしてCSRFトークンの情報を出力し、Ajax通信時にリクエストヘッダにトークン値を設定して連携する


9.5.2.2.1. Spring MVCを使用した連携

Spring Securityは、Spring MVCと連携するためのコンポーネントをいくつか提供している。
ここでは、CSRF対策機能と連携するためのコンポーネントの使い方を説明する。

9.5.2.2.1.1. JSPにおけるhidden項目の自動出力

HTMLフォームを作成する際は、以下のようなJSPの実装を行う。

  • JSPの実装例

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

<c:url var="loginUrl" value="/login"/>
<form:form action="${loginUrl}"> <!-- (1) -->
    <!-- omitted -->
</form:form>

項番

説明

(1)
HTMLフォームを作成する際は、Spring MVCから提供されている<form:form>要素を使用する。

Spring MVCから提供されている<form:form>要素を使うと、以下のようなHTMLフォームが作成される。

  • HTMLの出力例

<form id="command" action="/login" method="post">
    <!-- omitted -->
    <!-- Spring MVCの機能と連携して出力されたCSRFトークン値のhidden項目 -->
    <div>
        <input type="hidden"
               name="_csrf" value="63845086-6b57-4261-8440-97a3c6fa6b99" />
    </div>
</form>

Tip

出力されるCSRFトークンチェック値

CsrfRequestDataValueProcessorを使用すると、<form:form>タグのmethod属性に指定した値がCSRFトークンチェック対象のHTTPメソッド(Spring Securityのデフォルト実装ではGET,HEAD,TRACE,OPTIONS以外のHTTPメソッド)と一致する場合に限り、CSRFトークンが埋め込まれた<input type="hidden">タグが出力される。

例えば、以下の例のように method属性にGETメソッドを指定した場合は、CSRFトークンが埋め込まれた<input type="hidden">タグは出力されない。

<form:form method="GET" modelAttribute="xxxForm" action="...">
    <%-- ... --%>
</form:form>

これは、Cross-Site Request Forgery (CSRF) Prevention Cheat Sheetで説明されている内容に対応している事を意味しており、セキュアなWebアプリケーション構築の手助けとなる。


9.5.2.2.1.2. Thymeleafにおけるhidden項目の自動出力

HTMLフォームを作成する際は、以下のようにThymeleafのテンプレートHTMLを実装する。

  • テンプレートHTMLの実装例

<form th:action="@{/login}" method="post"> <!--/* (1) */-->
    <!--/* omitted */-->
</form>

項番

説明

(1)
HTMLフォームを作成する際は、Thymeleafのth:action属性を使用する。

Thymeleafのth:action属性を使うと、以下のようなHTMLフォームが作成される。

  • HTMLの出力例

<form action="/login" method="post">
    <!-- Spring MVCの機能と連携して出力されたCSRFトークン値のhidden項目 -->
    <input type="hidden"
        name="_csrf" value="63845086-6b57-4261-8440-97a3c6fa6b99" />
    <!-- omitted -->
</form>

Tip

出力されるCSRFトークンチェック値

CsrfRequestDataValueProcessorを使用すると、th:action属性が付与された<form>タグのmethod属性に指定した値がCSRFトークンチェック対象のHTTPメソッド(Spring Securityのデフォルト実装ではGET,HEAD,TRACE,OPTIONS以外のHTTPメソッド)と一致する場合に限り、CSRFトークンが埋め込まれた<input type="hidden">タグが出力される。

例えば、以下の例のように method属性にGETメソッドを指定した場合は、CSRFトークンが埋め込まれた<input type="hidden">タグは出力されない。

<form method="GET" th:object="${xxxForm}" th:action="@{...}">
    <!-- omitted -->
</form>

これは、Cross-Site Request Forgery (CSRF) Prevention Cheat Sheetで説明されている内容に対応している事を意味しており、セキュアなWebアプリケーション構築の手助けとなる。

なお、<form>要素にmethod属性が指定されていない場合、HTML5標準ではGETメソッドとして処理される。このため、CSRF対策機能を使用する場合、明示的にmethod属性にpostを指定する必要がある。

Note

自動的にCSRFトークンを埋め込みたいが、action属性を付与したくない場合

リクエストURLを生成する」で解説する「現在のパスからの相対パス」を利用することで、リクエストマッピングのパスが異なる複数のコントローラで同じテンプレートHTMLを使いまわすことが可能である。

「現在のパスからの相対パス」を使用すると、必ずページを取得したパスから派生する別のパスを指定する必要があるように見えるが、th:action属性の値を指定しないことで、出力されるaction属性の値が空になり、ページを取得したのと同じパスに対してリクエストを送信することが可能となる。(一般的なブラウザでは、action属性の値を空にすると、action属性を付与していないのと同じ動作となる。)

これを利用して、自動的にCSRトークンをhidden要素に埋め込みたいが、action属性を付与したくない(=ページを取得したのと同じパスに対してリクエストを送信したい)という要件を実現することが可能である。

以下に、th:action属性の値を指定しない例を示す。

<form th:action method="post">
    <!--/* omitted */-->
</form>

9.5.2.2.2. HTMLフォーム使用時の連携

JSPではSpring MVCと連携せずに、HTMLフォームを使用してCSRFトークン値を連携することも可能である。
HTMLフォームを使ってリクエストを送信する場合は、HTMLフォームのhidden項目としてCSRFトークン値を出力し、リクエストパラメータとして連携する。
  • JSPの実装例

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<form action="<c:url value="/login" />" method="post">
    <!-- omitted -->
    <sec:csrfInput /> <!-- (1) -->
    <!-- omitted -->
</form>

項番

説明

(1)
HTMLの<form>要素の中に<sec:csrfInput>要素を指定する。
Spring Securityから提供されている<sec:csrfInput>要素を指定すると、以下のようなhidden項目が出力される。
HTMLフォーム内にhidden項目を出力することで、CSRFトークン値がリクエストパラメータとして連携される。
デフォルトでは、CSRFトークン値を連携するためのリクエストパラメータ名は_csrfになる。
  • HTMLの出力例

<form action="/login" method="post">
    <!-- omitted -->
    <!-- CSRFトークン値のhidden項目 -->
    <input type="hidden"
          name="_csrf"
          value="63845086-6b57-4261-8440-97a3c6fa6b99" />
    <!-- omitted -->
</form>

Warning

GETメソッド使用時の注意点

HTTPメソッドとしてGETを使用する場合、<sec:csrfInput>要素を指定しないこと。

<sec:csrfInput>要素を指定してしまうと、URLにCSRFトークン値が含まれてしまうため、CSRFトークン値が盗まれるリスクが高くなる。


9.5.2.2.3. Ajax使用時の連携

9.5.2.2.3.1. JSPにおけるAjax使用時の連携

ViewにJSPを使用しAjaxを使ってリクエストを送信する場合は、HTMLのmetaタグとしてCSRFトークンの情報を出力し、metaタグから取得したトークン値をAjax通信時のリクエストヘッダに設定して連携する。

まず、Spring Securityから提供されているJSPタグライブラリを使用して、HTMLのmetaタグにCSRFトークンの情報を出力する。

  • JSPの実装例

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<head>
    <!-- omitted -->
    <sec:csrfMetaTags /> <!-- (1) -->
    <!-- omitted -->
</head>

項番

説明

(1)
HTMLの<head>要素内に<sec:csrfMetaTags>要素を指定する。
<sec:csrfMetaTags>要素を指定すると、以下のようなmetaタグが出力される。
デフォルトでは、CSRFトークン値を連携するためのリクエストヘッダ名はX-CSRF-TOKENとなる。
  • HTMLの出力例

<head>
    <!-- omitted -->
    <meta name="_csrf_parameter" content="_csrf" />
    <meta name="_csrf_header" content="X-CSRF-TOKEN" /> <!-- ヘッダ名 -->
    <meta name="_csrf"
          content="63845086-6b57-4261-8440-97a3c6fa6b99" /> <!-- トークン値 -->
    <!-- omitted -->
</head>

つぎに、JavaScriptを使ってmetaタグからCSRFトークンの情報を取得し、Ajax通信時のリクエストヘッダにCSRFトークン値を設定する。(ここではjQueryを使った実装例となっている)

  • JavaScriptの実装例

$(function () {
    var headerName = $("meta[name='_csrf_header']").attr("content"); // (1)
    var tokenValue = $("meta[name='_csrf']").attr("content"); // (2)
    $(document).ajaxSend(function(e, xhr, options) {
        xhr.setRequestHeader(headerName, tokenValue); // (3)
    });
});

項番

説明

(1)
CSRFトークン値を連携するためのリクエストヘッダ名を取得する。
(2)
CSRFトークン値を取得する。
(3)
リクエストヘッダにCSRFトークン値を設定する。

9.5.2.2.3.2. ThymeleafにおけるAjax使用時の連携

ViewにThymeleafを使用し、Ajaxを使ってリクエストを送信する場合は、CSRFトークンの情報をリクエストヘッダに設定して連携する。

JavaScriptの実装例を以下に示す。(ここではjQueryを使った実装例となっている)

  • JavaScriptの実装例

$(function () {
    $(document).ajaxSend(function(e, xhr, options) {
        xhr.setRequestHeader([[${_csrf.headerName}]], [[${_csrf.token}]]); // (1)
    });
});

項番

説明

(1)
JavaScriptのインライン記法を用いることでリクエストヘッダ名とCSRFトークン値を取得する。デフォルトでは、リクエストヘッダ名はX-CSRF-TOKENとなる。
JavaScriptにおけるインライン記法の詳細はテンプレートエンジン(Thymeleaf)のJavaScriptのテンプレート化を参照されたい。

9.5.2.3. トークンチェックエラー時の遷移先の制御

トークンチェックエラー時の遷移先の制御を行うためには、CSRFトークンチェックエラーに発生する例外である AccessDeniedExceptionをハンドリングして、その例外に対応した遷移先を指定する。

CSRFのトークンチェックエラー時に発生する例外は以下の通りである。

CSRFトークンチェックで使用される例外クラス

クラス名

説明

InvalidCsrfTokenException
クライアントから送られたトークン値と、サーバー側で保持しているトークン値が一致しない場合に使用する例外クラス(主に不正なリクエスト)。
MissingCsrfTokenException
サーバー側にトークン値が保存されていない場合に使用する例外クラス(主にセッション切れ)。

DelegatingAccessDeniedHandlerクラスを使用して上記の例外をハンドリングし、それぞれに AccessDeniedHandlerインタフェースの実装クラスを割り当てることで、例外毎の遷移先を設定することが可能である。

CSRFトークンチェックエラー時に専用のエラー画面に遷移させたい場合は、以下のようなBean定義を行う。(以下の定義例は、ブランクプロジェクトからの抜粋である)

  • SpringSecurityConfig.javaの定義例

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // omitted
    http.exceptionHandling(ex -> ex.accessDeniedHandler(
            accessDeniedHandler())); // (1)
    // omitted
    return http.build();
}

@Bean("accessDeniedHandler")
public AccessDeniedHandler accessDeniedHandler() {
    LinkedHashMap<Class<? extends AccessDeniedException>, AccessDeniedHandler> errorHandlers = new LinkedHashMap<>();

    // Invalid CSRF authenticator error handler
    AccessDeniedHandlerImpl invalidCsrfTokenErrorHandler = new AccessDeniedHandlerImpl();
    invalidCsrfTokenErrorHandler.setErrorPage(
            "/WEB-INF/views/common/error/invalidCsrfTokenError.jsp");
    errorHandlers.put(InvalidCsrfTokenException.class,
            invalidCsrfTokenErrorHandler); // (4)

    // Missing CSRF authenticator error handler
    AccessDeniedHandlerImpl missingCsrfTokenErrorHandler = new AccessDeniedHandlerImpl();
    missingCsrfTokenErrorHandler.setErrorPage(
            "/WEB-INF/views/common/error/missingCsrfTokenError.jsp");
    errorHandlers.put(MissingCsrfTokenException.class,
            missingCsrfTokenErrorHandler); // (5)

    // Default error handler
    AccessDeniedHandlerImpl defaultErrorHandler = new AccessDeniedHandlerImpl();
    defaultErrorHandler.setErrorPage(
            "/WEB-INF/views/common/error/accessDeniedError.jsp"); // (6)

    return new DelegatingAccessDeniedHandler(errorHandlers, defaultErrorHandler); // (2)(3)
}

項番

説明

(1)
HttpSecurity#exceptionHandlingExceptionHandlingConfigurer#accessDeniedHandlerに、Exception毎の制御を行うためのAccessDeniedHandlerのBean名を指定する。
エラー時遷移先が全て同じ画面である場合はerror-page属性に遷移先を指定すればよい。
ExceptionHandlingConfigurer#accessDeniedHandlerでハンドリングしない場合は、認可エラー時の遷移先を参照されたい。
(2)
DelegatingAccessDeniedHandlerを使用して、発生した例外(AccessDeniedExceptionサブクラス) と例外ハンドラ(AccessDeniedHandler実装クラス)を定義する。
(3)
コンストラクタの第1引数で、個別に遷移先を指定したい例外(AccessDeniedExceptionサブクラス)と、対応する例外ハンドラ(AccessDeniedHandler実装クラス)をMap形式で定義する。
(4)
keyAccessDeniedExceptionのサブクラスを指定する。
valueとしてAccessDeniedHandlerの実装クラスであるorg.springframework.security.web.access.AccessDeniedHandlerImplを指定する。
errorPagevalueに表示するviewを指定する。
マッピングするExceptionに関しては、トークンチェックエラー時の遷移先の制御を参照されたい。
(5)
(4)のExceptionと異なるExceptionを制御したい場合に定義する。
本例では InvalidCsrfTokenExceptionMissingCsrfTokenExceptionそれぞれに異なる遷移先を設定している。
(6)
コンストラクタの第2引数で、デフォルト例外((4)(5)で指定していないAccessDeniedExceptionのサブクラス)時の例外ハンドラ(AccessDeniedHandler実装クラス)と遷移先を指定する。

Note

無効なセッションを使ったリクエストの検知

セッション管理機能の「無効なセッションを使ったリクエストの検知」処理を有効にしている場合は、MissingCsrfTokenExceptionに対して「無効なセッションを使ったリクエストの検知」処理と連動するAccessDeniedHandlerインタフェースの実装クラスが適用される。

そのため、MissingCsrfTokenExceptionが発生すると、「無効なセッションを使ったリクエストの検知」処理を有効化する際に指定したパス(invalid-session-url)にリダイレクトする。

Note

ステータスコード403以外を返却したい場合

リクエストに含まれるCSRFトークンが一致しない場合に、ステータスコード403以外を返却したい場合は、org.springframework.security.web.access.AccessDeniedHandlerインタフェースを実装した、独自のAccessDeniedHandlerを作成する必要がある。