Skip to main content

Spring Securityの認可バイパス(CVE-2022-31692)を詳しく解説

feature spring security auth bypass

2022年12月16日

0 分で読めます

11月初旬、Spring Security 5で新たな認可バイパスの脆弱性が発見されました。慌てる前に、この問題を詳しく見て、自分が脆弱な状態にあるか確認しましょう。この脆弱性は高と評価されていますが、影響を受けるのは特定のユースケースに限られます。つまり、すべての環境が脆弱というわけではありません。その点については後ほど説明します。それでも、Spring Securityを新しいバージョンにアップグレードすることをお勧めします。バージョンは5.6.9以降、または5.7.5以降です。

認可バイパスとは

名前のとおり、認可をバイパスすることです。たとえば、Spring Bootアプリケーションに、管理者ロールを設定されたユーザーだけがアクセスできるWebページがあるとします。認可バイパスが起きると、管理者ではないユーザーが、特定のユースケースにおいて管理者ロールを持たないまま(あるいはそれ以上の権限を持たずに)そのページにアクセスできてしまいます。これは明らかに望ましくなく、データ漏えいや、データの不正な変更・作成・削除などにつながるおそれがあります。

このSpring Securityの認可バイパスはどのように発生するのか

Spring Securityでは、SecurityFilterChainを作成して、特定のエンドポイントに対する権限を設定できます。新しいバージョンのSpringでは、次のようになります。

@Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  http
     .authorizeHttpRequests((authorize) -> authorize
        .antMatchers("/").permitAll()
        .antMatchers("/forward").permitAll()
        .antMatchers("/admin").hasAuthority("ROLE_ADMIN")
        .shouldFilterAllDispatcherTypes(true)
     )
     .httpBasic().and()
     .userDetailsService(userDetailsService());
      return http.build();
  }

上記の例では、antMatchersを使って、HTTPメソッドに関係なくURLパターンの権限を設定しています。/と/forwardには誰でもアクセスできます。管理者ロールを持つユーザーだけが/adminのURLにアクセスできます。

ここまでは問題なく、特別なこともなく、正常に動作しています。次に、コントローラーでのforwardエンドポイントの実装を見てみましょう。

@GetMapping("/forward")
public String redirect() {
  return "forward:/admin";
}

上記の実装では、forwardエンドポイントがリクエストをadminエンドポイントに転送します。また、Spring Securityの設定にすべてのディスパッチャータイプを追加しました。デフォルトではrequest、async、errorのみが対象だからです。Spring Bootアプリケーションのapplication.propertiesに次の行を追加しました。

spring.security.filter.dispatcher-types = request, error, async, forward, include

これで問題なく、adminエンドポイントはフィルターで保護されていると思うかもしれません。しかし実際には、管理者ロールを持たず、ログインすらしていない状態で/forward経由でadminエンドポイントにアクセスできてしまいます。設定にforwardを明示的に追加し、shouldFilterAllDispatcherTypes()を有効にしていても、管理者ページにアクセスできるのです。forwardを使い、すべてのディスパッチタイプを含めることで、認可をバイパスし、より高い権限で保護されたエンドポイントにアクセスできてしまいます。

localhost:8080/forwardで「This is the Admin page」と表示されているブラウザーウィンドウ

Spring Security 5でこれが可能なのは、デフォルトでは1つのリクエストに対してフィルターが複数回適用されないためです。そのため、Spring Securityで明示的に設定する必要があります。さらに、FilterChainProxyはforwardおよびincludeのディスパッチャータイプで呼び出されるようにも設定されていません。

古いバージョンのSpring Security 5でも同じ問題が発生します

古いバージョンのSpringアプリケーションを実行している場合、Spring Securityも古いバージョンである可能性が高いでしょう。ワークショップ用に保守している古いSpring Bootアプリケーションは2.2.0-RELEASEをベースとしており、Spring Securityの5.2.0-RELEASEが含まれています。これらの古いバージョンでは、セキュリティの設定方法が少し異なります。WebSecurityConfigurerAdapterインターフェースを拡張する設定クラスを作成し、configure()メソッドをオーバーライドして、先ほどと同様のフィルターを実装する必要がありました。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http
               .authorizeRequests()
               .antMatchers("/").permitAll()
               .antMatchers("/forward").permitAll()
               .antMatchers("/admin").hasAuthority("ROLE_ADMIN");
   }
}

上記の例では、先ほどと同じような脆弱性を再現しました。問題が発生するのと同じフィルターチェーンを使用しています。

Spring Securityのこの問題はどれほど深刻なのか

いつものことですが、使い方によります。CVE-2022-31692は、National Vulnerability Database(NVD)によると9.8(クリティカル)のスコアですが、Snykでは少し低い7.4と評価しており、深刻度は高となっています。

この脆弱性は比較的簡単に悪用でき、広く知られていますが、実際に影響を受けるのは特定のユースケースに限られます。forwardとincludeのディスパッチタイプに依存し、上記の例のようにAuthorizationFiltersを使用している場合は、問題が発生する可能性があります。これらのディスパッチタイプで、より高い権限レベルを持つエンドポイントを呼び出し、かつ権限を指定するためにAuthorizationFilterを使用している場合、認可バイパスにつながるおそれがあります。

つまり、この脆弱性の影響を受けるには、多くの前提条件があります。詳しくは、このセキュリティ問題に関するアドバイザリをSnyk Vulnerability Databaseでご確認ください。

このセキュリティ問題を軽減する方法

最も簡単で確実な解決策は、Spring Securityを5.7.5以降にアップデートすることです。5.6.xブランチをお使いの場合は、5.6.9で修正されています。Spring Bootアプリケーションをお使いの場合、Spring Bootを2.7.6以降にアップデートすると、適切なバージョンのSpring Securityが自動的に含まれます。

アップデートできない場合は、フィルター定義をauthorizeHttpRequests().shouldFilterAllDispatcherTypes(true)から.authorizeRequests().filterSecurityInterceptorOncePerRequest(false)に変更できます。これにより、forwardディスパッチタイプが想定どおりにフィルタリングされます。

この記事の最初の例を書き換えると、修正後のコードは次のようになります。

@Configuration
public class SecurityConfig {

   @Bean
   public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
       http
               .authorizeRequests()
               .filterSecurityInterceptorOncePerRequest(false)
               .antMatchers("/").permitAll()
               .antMatchers("/forward").permitAll()
               .antMatchers("/admin").hasAuthority("ROLE_ADMIN")
               .and()
               .httpBasic().and()
               .userDetailsService(userDetailsService());
       return http.build();
   }
}

同様に、WebSecurityConfigurerAdapterを拡張する必要があった従来のフィルター定義にも、.filterSecurityInterceptorOncePerRequest(false)を追加できます。

依存関係をアップデートして安全を保つ

依存関係を常に最新の状態に保つことが重要だと、改めてわかります。このようなセキュリティ問題がアプリケーションに影響した際に依存関係が古いままだと、問題の軽減に大幅な手間がかかります。Snyk Open SourceのようなSCAスキャン機能を利用すれば、情報や修正方法を公開後すぐに確認できます。Snykは無料で利用できます。特にJava開発者向けに、利用を始める方法をわかりやすく解説した記事を書きました。

CTFを始めよう

オンデマンドのバーチャル入門ワークショップを視聴して、CTFの課題の解き方を学びましょう。