Spring MVCとThymeleafによるCSRF保護

1前書き

Thymeleaf は、HTML、XML、JavaScript、CSS、およびプレーンテキストを処理および作成するためのJavaテンプレートエンジンです。 ThymeleafとSpringのイントロについては、リンクを見てください:/thymeleaf-in-spring-mvc[この記事]。

この記事では、Thymeleafアプリケーションを使ってSpring MVCで クロスサイトリクエストフォージェリ(CSRF)攻撃を防ぐ 方法について説明します。具体的には、HTTP POSTメソッドに対するCSRF攻撃をテストします。

CSRFは、現在認証されているWebアプリケーションでエンドユーザーに不要なアクションを実行させる攻撃です。

2 Mavenの依存関係

まず、ThymeleafとSpringを統合するために必要な設定を見てみましょう。私たちの依存関係では thymeleaf-spring ライブラリが必要です。

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.0.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring4</artifactId>
    <version>3.0.9.RELEASE</version>
</dependency>

Spring 4プロジェクトでは、 thymeleaf-spring5 の代わりに thymeleaf-spring4 ライブラリを使用する必要があります。依存関係の最新バージョンはhttps://search.maven.org/classic/#search%7Cg%%CA%22org.thymeleaf%22%20AND%20a%3A%22thymeleaf-spring5%22[ここに]。

さらに、Spring Securityを使用するためには、以下の依存関係を追加する必要があります。

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>5.0.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.0.6.RELEASE</version>
</dependency>

2つのSpring Security関連ライブラリの最新版はhttps://search.maven.org/classic/#search%7Cgav%7C1%7Ca%3A%22spring-security-web%22[here]およびhttps://searchから入手可能.maven.org/classic/#search%7Cgav%7C1%7Ca%3A%22spring-security-config%22[ここ]。

3 Javaの設定

Thymeleaf設定カバーリンクに加えて:/thymeleaf-in-spring-mvc[ここ]、Spring Securityの設定を追加する必要があります。そのためには、クラスを作成する必要があります。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebMVCSecurity extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
          .withUser("user1").password("{noop}user1Pass")
          .authorities("ROLE__USER");
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/** ** ");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests()
          .anyRequest()
          .authenticated()
          .and()
          .httpBasic();
    }
}

セキュリティ設定の詳細と説明については、/security-spring[Security with Spring]シリーズのリンクを参照してください。

  • CSRF保護はJava設定でデフォルトで有効になっています。** この便利な機能を無効にするには、 configure(…​) メソッドにこれを追加する必要があります。

.csrf().disable()

XML構成では、CSRF保護を手動で指定する必要があります。そうしないと、機能しません。

<security:http
  auto-config="true"
  disable-url-rewriting="true"
  use-expressions="true">
    <security:csrf/>

    <!-- Remaining configuration ... -->
</security:http>

また、ログインページでログインページを使用している場合は、コード内に手動で隠しパラメータとしてCSRFトークンをログインフォームに含める必要があります。

<input
  type="hidden"
  th:name="${__csrf.parameterName}"
  th:value="${__csrf.token}"/>

残りのフォームでは、CSRFトークンは自動的に隠し入力を持つフォームに追加されます。

<input
  type="hidden"
  name="__csrf"
  value="32e9ae18-76b9-4330-a8b6-08721283d048"/>
<!-- Example token -->

4ビュー設定

フォームアクションとテスト手順の作成を使用してHTMLファイルの主要部分に進みましょう。最初のビューでは、リストに新しい生徒を追加しようとしています。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org">
<head>
<title>Add Student</title>
</head>
<body>
    <h1>Add Student</h1>
        <form action="#" th:action="@{/saveStudent}" th:object="${student}"
          method="post">
            <ul>
                <li th:errors="** {id}"/>
                <li th:errors="** {name}"/>
                <li th:errors="** {gender}"/>
                <li th:errors="** {percentage}"/>
            </ul>
    <!-- Remaining part of HTML -->
    </form>
</body>
</html>

このビューでは、 id name gender 、および percentage を指定して、生徒をリストに追加しています(オプションで、フォームの検証で説明されているとおり)。このフォームを実行する前に、Webアプリケーションで私たちを認証するために user password を提供する必要があります。

4.1. ブラウザCSRF攻撃テスト

今度は2番目のHTMLビューに進みます。その目的は、CSRF攻撃を試みることです。

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<form action="http://localhost:8080/spring-thymeleaf/saveStudent" method="post">
    <input type="hidden" name="payload" value="CSRF attack!"/>
    <input type="submit"/>
</form>
</body>
</html>

アクションURLは http://localhost:8080/spring-thymeleaf/saveStudent です。ハッカーはこのページにアクセスして攻撃を行いたいと考えています。

テストするには、アプリケーションにログインせずに別のブラウザでHTMLファイルを開きます。フォームを送信しようとすると、次のページが表示されます。

CSRFトークンなしでリクエストを送信したため、リクエストは拒否されました。

HTTPセッションはCSRFトークンを格納するために使用されることに注意してください。

リクエストが送信されると、Springはユーザーがハッキングされていないことを確認するために、生成されたトークンをセッションに格納されているトークンと比較します。

4.2. JUnit CSRF攻撃テスト

ブラウザを使用してCSRF攻撃をテストしたくない場合は、簡単な統合テストでも実行できます。そのテスト用のSpringの設定から始めましょう。

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {
  WebApp.class, WebMVCConfig.class, WebMVCSecurity.class, InitSecurity.class })
public class CsrfEnabledIntegrationTest {

   //configuration

}

そして実際のテストに進みます。

@Test
public void addStudentWithoutCSRF() throws Exception {
    mockMvc.perform(post("/saveStudent").contentType(MediaType.APPLICATION__JSON)
      .param("id", "1234567").param("name", "Joe").param("gender", "M")
      .with(testUser())).andExpect(status().isForbidden());
}

@Test
public void addStudentWithCSRF() throws Exception {
    mockMvc.perform(post("/saveStudent").contentType(MediaType.APPLICATION__JSON)
      .param("id", "1234567").param("name", "Joe").param("gender", "M")
      .with(testUser()).with(csrf())).andExpect(status().isOk());
}

最初のテストではCSRFトークンが欠落しているために禁止されたステータスになりますが、2番目のテストは正しく実行されます。

5結論

この記事では、Spring SecurityとThymeleafフレームワークを使ってCSRF攻撃を防ぐ方法について説明しました。

このチュートリアルの完全な実装はhttps://github.com/eugenp/tutorials/tree/master/spring-thymeleaf[GitHubプロジェクト]で見つけることができます - これはEclipseベースのプロジェクトです。そのまま実行します。