SpringSecurityで学ぶセッションとセッション固定化攻撃

2022.10.10

「体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践」という本を買っていたので、セキュリティに関して少し勉強しました。

セッションを扱う時には以下の攻撃に注意する必要があります

  • セッションハイジャック
  • セッション固定化

セキュリティというのはフレームワークに任せておけば大丈夫だろうと思っていたのですが、一体どういうことが起きているのか知るのはとても面白いです。

今回はセッション固定化攻撃を通して、SpringSecurityでのセッションについて学んでいきたいと思います。また、危険なアプリケーションを作ることで、作らないように気を付けようという事も意識していきます。

セッション固定化攻撃の流れとしてURLにセッションIDを付与する方法(参考書と同様の方法)があるので、それで危険なアプリケーションを作っていきます。

SpringSecurityで危険なアプリケーションを作らないようにしようと言っても、SpringSecurityではURLにセッションIDを指定することが通常はできないようです。つまり普通に使っていれば安全です。

参考サイト

https://qiita.com/d-yosh/items/f52372d7190fd8af98f0

http://terasolunaorg.github.io/guideline/current/ja/Security/SpringSecurity.html

基本動作

セッションIDというのはユーザをアプリケーションが識別するためのKEYです。通常これはブラウザ側のクッキーに保存します。

SpringSecurityを入れたアプリケーションでログインすると以下のようなクッキーが保存されていることがわかります

画像

JSESSIONIDというのがセッションIDを表すクッキー名です。これはサーブレットの仕様で決められた値のようです

http://itdoc.hitachi.co.jp/manuals/3020/30203M0360/EM030096.HTM

このセッションIDを使って、そのブラウザがログインしているかどうかをアプリケーションが識別しますので、ログイン後にJSESSIONIDを削除すると未ログインとして扱われます。

URL Rewriting

Cookieを使用できないクライアントとのセッション維持のためにURLのリクエストパラメータにセッションIDを含める方法。

もはやデフォルトでセッション固定化攻撃に対して防衛ができているのですが、今回はあえてURL Rewritingを有効にします。

URL Rewritingの有効化

SessionManagementConfigurerのenableSessionUrlRewritingをtrueにしてあげますので、HttpSecurityの設定を変更してあげます

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.formLogin( form -> {
                form.loginPage("/login");
                form.loginProcessingUrl("/login");
                form.usernameParameter("username");
                form.passwordParameter("password");
                form.defaultSuccessUrl("/", true);
                form.failureUrl("/login?error");
    });
    http.authorizeRequests(auth -> {
       auth.antMatchers("/login").permitAll();
       auth.anyRequest().authenticated();
    });
    http.sessionManagement().enableSessionUrlRewriting(true);//これで有効化
    return http.build();
}

次にURLのパラメータにセッションIDを付与するためにSessionTrackingModeを利用してサーブレットコンテナ設定をします。こちらは上に書いた参考サイトと下記サイトから。

https://qiita.com/dmnlk/items/b7d189d4dc09df1ee6b6

@Bean
public ServletContextInitializer servletContextInitializer(){
    //SessionTrackingModeのURLを指定する
    return servletContext -> servletContext.setSessionTrackingModes(EnumSet.of(SessionTrackingMode.URL));
}

ログインするとURLにセッションIDが付与されているのがわかります

画像

そして参考サイトと同じRequestRefectedExceptionが発生しました。

というわけで、ここも参考サイトに従ってDefaultHttpFirewallを利用します

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    DefaultHttpFirewall defaultHttpFirewall = new DefaultHttpFirewall();
    return web -> web.httpFirewall(defaultHttpFirewall);
}

セッションIDの確認

先ほどの設定でログインに成功できるようになります。

ログインページにアクセスしてURLにセッションIDが付与されていることを再度確認します。

画像

この状態でログインするとセッションIDが変更されています。

画像

これは非常に重要で、セッション固定化攻撃を防いでいます。ちなみにセッションハイジャックは可能です。このログインしたアドレスに別ブラウザでアクセスするとログインされた状態になります

画像

URLパラメータにセッションIDを付与していると常に危険が隣り合わせということですね。またセッションIDはログイン時に新しくしておくことも重要です。既存のセッションIDを利用すると攻撃対象になるからです。その辺について見ていきます。

セッション固定化攻撃

このままでは、ログインするとセッションIDが新しくなり、セッション固定化ができないので、以下のように書き変えます

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.formLogin( form -> {
                form.loginPage("/login");
                form.loginProcessingUrl("/login");
                form.usernameParameter("username");
                form.passwordParameter("password");
                form.defaultSuccessUrl("/", true);
                form.failureUrl("/login?error");
    });
    http.authorizeRequests(auth -> {
       auth.antMatchers("/login").permitAll();
       auth.anyRequest().authenticated();
    });
    http.sessionManagement().enableSessionUrlRewriting(true);
    http.sessionManagement().sessionFixation().none(); // セッション固定化攻撃対策をしない
    return http.build();
}

更に設定が必要でした。SessionCreationPolicy.NEVERを指定してあげます。

https://www.javadevjournal.com/spring-security/spring-security-session/

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.formLogin( form -> {
                form.loginPage("/login");
                form.loginProcessingUrl("/login");
                form.usernameParameter("username");
                form.passwordParameter("password");
                form.defaultSuccessUrl("/", true);
                form.failureUrl("/login?error");
    });
    http.authorizeRequests(auth -> {
       auth.antMatchers("/login").permitAll();
       auth.anyRequest().authenticated();
    });
    http.sessionManagement().enableSessionUrlRewriting(true);
    http.sessionManagement().sessionFixation().none();
    // セッションがすでにあればそれを利用する
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
    return http.build();
}

これでようやく準備ができました。

攻撃

攻撃者がログインページを表示します。表示されたアドレスにはセッションIDが付与されているのがポイントです。

画像

被害者がこのアドレスを利用してログインしたとします

画像

攻撃者はこのセッションIDを利用して、ログイン必須ページにアクセスできてしまいます。

画像

これからわかるようにセッションを扱う際には注意が必要です

  • URLパラメータにセッションIDを付与してはいけない
  • ログイン後にセッションIDを新しくする
    • SessionCreationPolicy.NEVERは使わない

クッキーが使えない環境では、URLパラメータにセッションIDを付与する必要があるかと思いますが、ログイン後にセッションIDを新しく発行するなど対応が必須になります。

また、普通にSpringSecurityを利用していれば、攻撃に対してフレームワークが対応してくれていることがわかりました。