8
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring Boot + Spring Security のセキュリティ対策 まとめ

Last updated at Posted at 2024-03-25

はじめに

書籍「 体系的に学ぶ 安全な Web アプリケーションの作り方 」では、PHP を使用したサンプルコードを例に挙げて、Web アプリケーションのセキュリティについて解説しています。

この内容を受け、Spring Boot と Spring Security の組み合わせにおけるデフォルトの設定や対策機能についても気になったので調べてみました。この記事はその備忘録です。

主に同書籍の第 4 章で取り上げられている脆弱性を対象とし、公式ドキュメントを中心にまとめました。
OS コマンドインジェクションや安全ではないデシリアライゼーションなど、Spring Framework での対策が見つけられなかった脆弱性に関しては言及していません。

以下の環境で動作確認を行いながら進めました。

  • Java 17
  • Spring Boot 3.2.3
    • spring-boot-starter-security
    • mybatis-spring-boot-starter
    • spring-boot-starter-thymeleaf
  • OWASP ZAP 2.12.0

誤りなどがありましたら、お知らせいただけますと幸いです。

XSS

一般的な対策

  • コンテキストに応じた特殊文字や制御文字のエスケープを行う
  • Cookie に HttpOnly 属性を付与する
  • ユーザ向けに詳細なエラーメッセージを表示しない
  • Content-Type ヘッダを正しく設定する
  • X-Content-Type-Options: nosniff ヘッダを設定する
  • CSP を使用して、許可されたソースからのみスクリプトやリソースを読み込む

Spring Framework の対策

HTTP レスポンスヘッダ

Spring Security のデフォルト設定では、OWASP の推奨事項に従い、X-XSS-Protection ヘッダに 0 が設定されています。
また、コンテンツの MIME タイプをスニッフィングさせないために、X-Content-Type-Options: nosniff ヘッダもデフォルトで追加されています。

しかし、コンテキストに応じた設定が必要な Content Security Policy (CSP) ヘッダは、デフォルトでは追加されていないため、必要に応じて設定しなければなりません。

以下は CSP ヘッダを設定する例です。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.headers(headers -> headers
				.contentSecurityPolicy(csp -> csp
					.policyDirectives("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
				)
			);
		return http.build();
	}
}

セッション Cookie に関しては、Spring Security では直接制御しないため、サーブレットコンテナで設定を行う必要があります。

ただし、Spring Boot を使用し、組み込みのサーブレットコンテナを使用している場合は、server.servlet.session.cookie.* プロパティを使用して変更することができます。

参考

Thymeleaf の対策

HTML エスケープ

Thymeleaf では基本的な XSS 対策が施されているため、適切に使用すれば安全です。

ただし、th:utext[(...)] など、値を HTML エスケープせずに出力する機能もあります。これらを使用してユーザからの入力値を出力する場合は、十分な検証やエスケープ処理を行うことが重要です。

参考

SQL インジェクション

一般的な対策

  • プレースホルダを使用して SQL を組み立てる
  • データベースには最小限の権限を付与する
  • ユーザ向けに詳細なエラーメッセージを表示しない

MyBatis の対策

プレースホルダ

MyBatis では、SQL 文の中に変数を埋め込む際に #{...} を使用することが推奨されています。この方法を使用すると、MyBatis が内部で PreparedStatement を使用して安全に値を埋め込むため、SQL インジェクション攻撃から保護されます。

しかし、ORDER BY 句のように、動的に列名のようなメタ情報を埋め込みたい場合もあります。そのようなときは、代わりに ${...} を使用できますが、この方法ではエスケープが行われません。
そのため、ユーザからの入力値を使用したい場合は、適切な検証やエスケープ処理を行うことで、安全性を確保することが重要です。

参考

CSRF

一般的な対策

  • 秘密情報 (CSRF トークン) を埋め込む
  • Referer を確認する
  • Cookie に SameSite 属性を付与する
  • 変更を伴うリクエストに GETHEADOPTIONSTRACE メソッドを使用しない
  • 重要な操作には二段階認証を使用する

Spring Framework の対策

HTTP レスポンスヘッダ

組み込みのサーブレットコンテナを使用する Spring Boot では、server.servlet.session.cookie.same-site プロパティでセッション Cookie の SameSite 属性を変更できます。

そのほかの Cookie の SameSite 属性を変更したい場合は、以下のように CookieSameSiteSupplier を使って設定します。

@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {

    @Bean
    public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
        return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
    }
}

参考

CSRF トークン

Spring Security では、デフォルトで CSRF 対策機能が有効になっています。

CSRF トークンは、安全でランダムな値として自動生成され、フォームの hidden フィールドや HTTP ヘッダに埋め込まれます。
リクエストを送信する際には、このトークンも一緒に送信するため、サーバ側では、受信したトークンと期待される値を比較することで、リクエストが正当なものであるかを確認します。
これにより、CSRF 攻撃からアプリケーションを保護することができます。

なお、デフォルトでは POSTPUTDELETEPATCH メソッドを使用した HTTP リクエストに対してのみ CSRF トークンチェックが行われます。

参考

クリックジャッキング

一般的な対策

  • X-Frame-Options ヘッダを使用して、外部からのフレーム埋め込みを制御する

Spring Framework の対策

HTTP レスポンスヘッダ

Spring Security のデフォルトでは、X-Frame-Options: DENY ヘッダを設定して、iframe 内のページのレンダリングを無効にしています。

参考

セッションハイジャック

一般的な対策

  • 推測が困難なセッション ID を生成する
  • セッション ID は Cookie を使って連携する
  • Cookie に HttpOnly 属性を付与する
  • Cookie に Secure 属性を付与する
  • HTTPS を使って通信を暗号化する

Spring Framework の対策

セッション ID の生成と管理

セッション ID の生成と管理はサーブレットコンテナが行うため、開発者が直接操作する必要はありません。

セッション ID の生成方法はサーブレットコンテナごとに異なります。
たとえば Tomcat のデフォルトでは 16 進数表記された 32 文字のランダムな文字列として生成し、JSESSIONID という名前の Cookie に格納します。

参考

URL Rewriting

URL Rewriting とは、Cookie を使用できないクライアントとセッションを維持するための仕組みのことで、サーブレットコンテナによって提供されます。

この仕組みでは、URL のリクエストパラメータを使ってサーバとクライアントの間でセッション ID を共有します。
たとえば、http://localhost:8080/;jsessionid=3ACB490E688E2C3BC729B277EC3D6A7D の場合、jsessionid=3ACB490E688E2C3BC729B277EC3D6A7D の部分が URL Rewriting されたセッション ID です。

しかし、この URL Rewriting は、セッション ID を漏洩させる危険性を高いため、無効化することが望ましいとされています。
Spring Security においても、デフォルトでセミコロンを含む URL を無効なリクエストとして判断するため、URL Rewriting によるセッション ID の連携は行えません。

参考

セッション固定攻撃

一般的な対策

  • セッション ID は Cookie を使って連携する
  • 認証後にセッション ID を変更する

Spring Framework の対策

認証後のセッション ID 変更

Spring Security では、デフォルトでユーザがログインするときにセッション ID を変更する仕組みが有効になっています。

参考

HTTP ヘッダインジェクション

一般的な対策

  • 外部からの入力値を直接 HTTP ヘッダとして出力しない
  • HTTP ヘッダの出力部分は言語やライブラリが提供するヘッダ出力用 API を使用する
  • HTTP ヘッダに含まれる特殊文字や制御文字をエスケープする

Spring Framework の対策

HTTP ヘッダのバリデーション

Servlet API では、HttpServletResponseWrapper クラスを使用してレスポンスをカスタマイズできます。

Spring Security のデフォルトでは、このクラスを拡張した FirewalledResponse というクラスを適用して、HTTP ヘッダの検証を行っています。
具体的には、レスポンスヘッダに値を設定するときに、入力値に改行文字が含まれていないかを検証し、含まれていた場合は IllegalArgumentException を発生させます。

この仕組みにより、改行文字を含む不正なヘッダ値が設定されることを防ぎ、セキュリティを向上させることができます。

参考

メールヘッダインジェクション

一般的な対策

  • 外部からの入力値を直接メールヘッダとして出力しない
  • メールの作成や送信には、専用のライブラリを使用する
  • メールヘッダに含まれる特殊文字や制御文字はエスケープする

Spring Framework の対策

特殊文字のエスケープ

Spring Framework では、メール送信に関する詳細なロジックを隠蔽し、低レベルの API ハンドリングを代行するためのユーティリティライブラリが提供されています。

これらのライブラリは、内部で MimeMessage クラスを使用しており、このクラスでは非 ASCII 文字や特殊文字を含むテキストをメールセーフ形式にエンコードします。これにより、メールの安全性や信頼性が確保されます。

参考

ディレクトリトラバーサル

一般的な対策

  • ファイルパスを指定するときは、外部からの入力値を使用しない
  • ファイルパスを外部から受け取る場合は、特殊文字や制御文字はエスケープする
  • アクセス権を適切に管理し、ユーザがアクセス可能なディレクトリやファイルを制限する

Spring Framework の対策

正規化されていないリクエスト

Spring Security では、HttpServletRequestHttpServletResponse に対してファイアウォール機能を組み込むために HttpFirewall インタフェースが提供されています。

このインタフェースにはいくつかの実装クラスが用意されていますが、Spring Security のデフォルトでは StrictHttpFirewall クラスが使用されています。
このクラスは、パストラバーサルシーケンス (/../ など) を含むような正規化されていないリクエストを拒否するなど、厳格な検証を行います。

参考

意図しないファイル公開

一般的な対策

  • Web サーバ上の非公開ディレクトリを使用する
  • ディレクトリリスティングを無効にする

Spring Framework の対策

Spring Boot では、ディレクトリリスティングが無効になっているようですが、明確な情報を見つけられませんでした。
調べる過程で参考にしたサイトを記します。

ファイルアップロード機能に対する DoS 攻撃

一般的な対策

  • ファイル当たりのサイズ制限を設ける
  • リクエストのボディサイズ制限を設ける

Spring Framework の対策

サイズ制限

Spring Boot では spring.servlet.multipart.* プロパティを使用して、ファイルアップロードに関する設定を行うことができます。

DoS 攻撃対策としては、max-file-size でアップロードを許可する 1 ファイルあたりの最大バイト数を指定し、max-request-sizemultipart/form-data リクエストの Content-Length の最大値を指定します。

これらの値を超えた場合は、org.springframework.web.multipart.MaxUploadSizeExceededException が発生します。

名前 説明 デフォルト値
spring.servlet.multipart.enabled マルチパートアップロードのサポートを有効にするかどうか true
spring.servlet.multipart.file-size-threshold ファイルがディスクに書き込まれるまでのしきい値 0B
spring.servlet.multipart.location アップロードされたファイルの中間場所
spring.servlet.multipart.max-file-size 最大ファイルサイズ 1MB
spring.servlet.multipart.max-request-size 最大リクエストサイズ 0MB
spring.servlet.multipart.resolve-lazily ファイルまたはパラメーターのアクセス時にマルチパートリクエストを遅延解決するかどうか false
spring.servlet.multipart.strict-servlet-compliance サーブレット仕様に厳密に従ってマルチパートリクエストを解決するかどうか false

参考

ファイルインクルード攻撃

一般的な対策

  • 外部からの入力値をインクルードするファイルパスに使用しない
  • ファイルパスを外部から受け取る場合は、特殊文字や制御文字はエスケープする
  • 外部ファイルの参照を無効にする

Thymeleaf の対策

テンプレートのインクルード

Thymeleaf では th:insertth:replace を使用して、テンプレート内の一部を別のテンプレートからインクルードすることができます。

さらに、UrlTemplateResolver を使用してテンプレートエンジンを構成することで、外部サーバ上にあるテンプレートをインクルードすることも可能になるようです。

これらを使用する際に、テンプレート名やフラグメント名としてユーザが入力した値を使用するならば、適切な検証を行って対策を行わなければなりません。

※ th:include は、Thymeleaf 3.1 で非推奨化されました。

参考

キャッシュからの情報漏洩

一般的な対策

  • 認証が必要な情報やユーザ毎に表示内容が異なる個人情報をキャッシュしない
  • キャッシュサーバやレスポンスヘッダの設定を適切に行う

Spring Framework の対策

HTTP レスポンスヘッダ

Spring Security のデフォルトでは、ユーザのコンテンツを保護するためにキャッシュが無効となっています。

デフォルトのキャッシュ制御 HTTP レスポンスヘッダは以下のとおりです。

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0

特定のレスポンスをキャッシュしたい場合は、HttpServletResponse.setHeader() メソッドを使ってこれらの値をオーバーライドします。

また、デフォルトでは有効になっていませんが、ログアウトするときに Clear-Site-Data ヘッダを送信する設定も簡単に行うことができます。

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			// ...
			.logout((logout) -> logout
                .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(CACHE, COOKIES)))
			);
		return http.build();
	}
}

参考

JSON エスケープの不備

一般的な対策

  • 信頼できるライブラリを用いて JSON を生成する

Spring Framework の対策

JSON レスポンスの生成

Spring MVC では、@ResponseBody アノテーションが付与されたコントローラメソッドの返り値は、HttpMessageConverter によってクライアントが要求したメディアタイプに自動的に変換されます。

とくに、Spring Boot の場合は、デフォルトで Jackson ライブラリが含まれており、JSON 形式のレスポンスを処理する MappingJackson2HttpMessageConverter が有効になっています。

そのため、Accept ヘッダに application/json が指定されたリクエストを受け取ると、自動的に MappingJackson2HttpMessageConverter が選択され、コントローラメソッドの戻り値は Jackson によって JSON 形式に変換されてからレスポンスボディに書き込まれます。

Jackson は、JSON 文字列を安全に表現するためにバックスラッシュやダブルクォートをエスケープしますが、HTML エスケープなどは行いません。そのため、それらが必要な場合はカスタマイズを行う必要があります。

文字エンコーディングの不備

Spring Framework の対策

文字エンコードの強制

組み込みのサーブレットコンテナを使用する Spring Boot では、server.servlet.encoding.* プロパティを使用して、リクエストおよびレスポンス処理のための文字エンコード動作を変更できます。

具体的には、server.servlet.encoding.enabledtrue に設定すると、HTTP エンコーディングサポートが有効になり、リクエストごとに CharacterEncodingFilter.doFilterInternal() メソッドが呼び出されます。
このメソッドでは force-request が true の場合、リクエストの文字エンコーディングを charset で指定された文字エンコーディングに強制的に上書きします。

名前 説明 デフォルト値
server.servlet.encoding.charset HTTP リクエストおよびレスポンスの文字セット UTF-8
server.servlet.encoding.enabled HTTP エンコーディングサポートを有効にするかどうか true
server.servlet.encoding.force HTTP リクエストおよびレスポンスで設定された文字セットにエンコードを強制するかどうか
server.servlet.encoding.force-request HTTP リクエストで構成された文字セットにエンコードを強制するかどうか force に従うが、force が未指定の場合は true
server.servlet.encoding.force-response HTTP レスポンスで構成された文字セットにエンコードを強制するかどうか force に従うが、force が未指定の場合は false
server.tomcat.uri-encoding URI のデコードに使用する文字エンコード UTF-8

※ Spring Boot 2.0.3 において spring.http.encoding.* から server.servlet.encoding.* に変更されました。

参考

8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?