はじめに
書籍「 体系的に学ぶ 安全な 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.*
プロパティを使用して変更することができます。
参考
- X-XSS-Protection | セキュリティ HTTP レスポンスヘッダー :: Spring Security - リファレンス (pleiades.io)
- コンテンツ型オプション | セキュリティ HTTP レスポンスヘッダー :: Spring Security - リファレンス (pleiades.io)
- コンテンツセキュリティポリシー (CSP) | セキュリティ HTTP レスポンスヘッダー :: Spring Security - リファレンス (pleiades.io)
- Java 構成 :: Spring Security - リファレンス (pleiades.io)
- server.servlet.session.cookie.http-only | Spring Boot アプリケーションプロパティ設定一覧 - リファレンス (pleiades.io)
- Secure Session Cookie | Control the Session with Spring Security - Baeldung
Thymeleaf の対策
HTML エスケープ
Thymeleaf では基本的な XSS 対策が施されているため、適切に使用すれば安全です。
ただし、th:utext
や [(...)]
など、値を HTML エスケープせずに出力する機能もあります。これらを使用してユーザからの入力値を出力する場合は、十分な検証やエスケープ処理を行うことが重要です。
参考
SQL インジェクション
一般的な対策
- プレースホルダを使用して SQL を組み立てる
- データベースには最小限の権限を付与する
- ユーザ向けに詳細なエラーメッセージを表示しない
MyBatis の対策
プレースホルダ
MyBatis では、SQL 文の中に変数を埋め込む際に #{...}
を使用することが推奨されています。この方法を使用すると、MyBatis が内部で PreparedStatement
を使用して安全に値を埋め込むため、SQL インジェクション攻撃から保護されます。
しかし、ORDER BY
句のように、動的に列名のようなメタ情報を埋め込みたい場合もあります。そのようなときは、代わりに ${...}
を使用できますが、この方法ではエスケープが行われません。
そのため、ユーザからの入力値を使用したい場合は、適切な検証やエスケープ処理を行うことで、安全性を確保することが重要です。
参考
CSRF
一般的な対策
- 秘密情報 (CSRF トークン) を埋め込む
-
Referer
を確認する - Cookie に
SameSite
属性を付与する - 変更を伴うリクエストに
GET
、HEAD
、OPTIONS
、TRACE
メソッドを使用しない - 重要な操作には二段階認証を使用する
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.*");
}
}
参考
- SameSite クッキー | Spring Boot Web - リファレンスドキュメント (pleiades.io)
- SameSite 属性 | クロスサイトリクエストフォージェリ(CSRF): : Spring Security - リファレンス (pleiades.io)
- 埋め込みサーブレットコンテナーのカスタマイズ | Spring Boot Web - リファレンスドキュメント (pleiades.io)
- server.servlet.session.cookie.same-site | Spring Boot アプリケーションプロパティ設定一覧 - リファレンス (pleiades.io)
CSRF トークン
Spring Security では、デフォルトで CSRF 対策機能が有効になっています。
CSRF トークンは、安全でランダムな値として自動生成され、フォームの hidden フィールドや HTTP ヘッダに埋め込まれます。
リクエストを送信する際には、このトークンも一緒に送信するため、サーバ側では、受信したトークンと期待される値を比較することで、リクエストが正当なものであるかを確認します。
これにより、CSRF 攻撃からアプリケーションを保護することができます。
なお、デフォルトでは POST
、 PUT
、 DELETE
、 PATCH
メソッドを使用した HTTP リクエストに対してのみ CSRF トークンチェックが行われます。
参考
- シンクロナイザートークンパターン | クロスサイトリクエストフォージェリ(CSRF):: Spring Security - リファレンス (pleiades.io)
- CSRF 保護との統合 | クロスサイトリクエストフォージェリ(CSRF): : Spring Security - リファレンス (pleiades.io)
クリックジャッキング
一般的な対策
-
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 に格納します。
参考
- 認証の永続性とセッション管理 ::Spring Security - リファレンス (pleiades.io)
- Apache Tomcat 9 Configuration Reference (9.0.86) - The SessionIdGenerator Component
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 の連携は行えません。
参考
- HttpFirewall :: Spring Security - リファレンス (pleiades.io)
- StrictHttpFirewall (spring-security-docs API) - Javadoc (pleiades.io)
- Prevent Using URL Parameters for Session Tracking | Control the Session with Spring Security - Baeldung
- Session Management - OWASP Cheat Sheet Series
セッション固定攻撃
一般的な対策
- セッション ID は
Cookie
を使って連携する - 認証後にセッション ID を変更する
Spring Framework の対策
認証後のセッション ID 変更
Spring Security では、デフォルトでユーザがログインするときにセッション ID を変更する仕組みが有効になっています。
参考
- セッション固定攻撃保護について | 認証の永続性とセッション管理 ::Spring Security - リファレンス (pleiades.io)
- ChangeSessionIdAuthenticationStrategy (spring-security-docs API) - Javadoc (pleiades.io)
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 - リファレンスドキュメント (pleiades.io)
- MimeMessage (Jakarta EE Platform API) - Javadoc (pleiades.io)
ディレクトリトラバーサル
一般的な対策
- ファイルパスを指定するときは、外部からの入力値を使用しない
- ファイルパスを外部から受け取る場合は、特殊文字や制御文字はエスケープする
- アクセス権を適切に管理し、ユーザがアクセス可能なディレクトリやファイルを制限する
Spring Framework の対策
正規化されていないリクエスト
Spring Security では、HttpServletRequest
と HttpServletResponse
に対してファイアウォール機能を組み込むために HttpFirewall
インタフェースが提供されています。
このインタフェースにはいくつかの実装クラスが用意されていますが、Spring Security のデフォルトでは StrictHttpFirewall
クラスが使用されています。
このクラスは、パストラバーサルシーケンス (/../
など) を含むような正規化されていないリクエストを拒否するなど、厳格な検証を行います。
参考
- HttpFirewall :: Spring Security - リファレンス (pleiades.io)
- StrictHttpFirewall (spring-security-docs API) - Javadoc (pleiades.io)
- CVE-2018-1199: Security bypass with static resources (spring.io)
意図しないファイル公開
一般的な対策
- Web サーバ上の非公開ディレクトリを使用する
- ディレクトリリスティングを無効にする
Spring Framework の対策
Spring Boot では、ディレクトリリスティングが無効になっているようですが、明確な情報を見つけられませんでした。
調べる過程で参考にしたサイトを記します。
- デフォルトのサーブレット :: Spring Framework - リファレンス (pleiades.io)
- 静的コンテンツ :: Spring Boot Web - リファレンスドキュメント (pleiades.io)
- 静的リソース :: Spring Framework - リファレンス (pleiades.io)
- Apache Tomcat 9 (9.0.87) - Default Servlet Reference
ファイルアップロード機能に対する DoS 攻撃
一般的な対策
- ファイル当たりのサイズ制限を設ける
- リクエストのボディサイズ制限を設ける
Spring Framework の対策
サイズ制限
Spring Boot では spring.servlet.multipart.*
プロパティを使用して、ファイルアップロードに関する設定を行うことができます。
DoS 攻撃対策としては、max-file-size
でアップロードを許可する 1 ファイルあたりの最大バイト数を指定し、max-request-size
で multipart/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 |
参考
- 入門 | ファイルのアップロード (pleiades.io)
- マルチパート :: Spring Framework - リファレンス (pleiades.io)
- spring.servlet.multipart.enabled | Spring Boot アプリケーションプロパティ設定一覧 - リファレンス (pleiades.io)
- MultipartFile (Spring Framework API) - Javadoc (pleiades.io)
ファイルインクルード攻撃
一般的な対策
- 外部からの入力値をインクルードするファイルパスに使用しない
- ファイルパスを外部から受け取る場合は、特殊文字や制御文字はエスケープする
- 外部ファイルの参照を無効にする
Thymeleaf の対策
テンプレートのインクルード
Thymeleaf では th:insert
や th:replace
を使用して、テンプレート内の一部を別のテンプレートからインクルードすることができます。
さらに、UrlTemplateResolver
を使用してテンプレートエンジンを構成することで、外部サーバ上にあるテンプレートをインクルードすることも可能になるようです。
これらを使用する際に、テンプレート名やフラグメント名としてユーザが入力した値を使用するならば、適切な検証を行って対策を行わなければなりません。
※ th:include は、Thymeleaf 3.1 で非推奨化されました。
参考
- Fragment specification syntax | Thymeleaf Page Layouts - Thymeleaf
- Including with Markup Selectors | Thymeleaf Page Layouts - Thymeleaf
- Template Resolvers | Tutorial: Using Thymeleaf
- Rendering Template Fragments | Tutorial: Thymeleaf + Spring
- Thymeleaf 3.1: What’s new and how to migrate - Thymeleaf
キャッシュからの情報漏洩
一般的な対策
- 認証が必要な情報やユーザ毎に表示内容が異なる個人情報をキャッシュしない
- キャッシュサーバやレスポンスヘッダの設定を適切に行う
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();
}
}
参考
- キャッシュ制御 | セキュリティ HTTP レスポンスヘッダー :: Spring Security - リファレンス (pleiades.io)
- サーブレット キャッシュ制御 | セキュリティ HTTP レスポンスヘッダー :: Spring Security - リファレンス (pleiades.io)
- サイトデータのクリア | セキュリティ HTTP レスポンスヘッダー :: Spring Security - リファレンス (pleiades.io)
JSON エスケープの不備
一般的な対策
- 信頼できるライブラリを用いて JSON を生成する
Spring Framework の対策
JSON レスポンスの生成
Spring MVC では、@ResponseBody
アノテーションが付与されたコントローラメソッドの返り値は、HttpMessageConverter
によってクライアントが要求したメディアタイプに自動的に変換されます。
とくに、Spring Boot の場合は、デフォルトで Jackson
ライブラリが含まれており、JSON 形式のレスポンスを処理する MappingJackson2HttpMessageConverter
が有効になっています。
そのため、Accept
ヘッダに application/json
が指定されたリクエストを受け取ると、自動的に MappingJackson2HttpMessageConverter
が選択され、コントローラメソッドの戻り値は Jackson
によって JSON 形式に変換されてからレスポンスボディに書き込まれます。
Jackson
は、JSON 文字列を安全に表現するためにバックスラッシュやダブルクォートをエスケープしますが、HTML エスケープなどは行いません。そのため、それらが必要な場合はカスタマイズを行う必要があります。
- Spring Boot REST API の作成 - 公式サンプルコード (pleiades.io)
- Http Message Converters with the Spring Framework | Baeldung
文字エンコーディングの不備
Spring Framework の対策
文字エンコードの強制
組み込みのサーブレットコンテナを使用する Spring Boot では、server.servlet.encoding.*
プロパティを使用して、リクエストおよびレスポンス処理のための文字エンコード動作を変更できます。
具体的には、server.servlet.encoding.enabled
を true
に設定すると、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.* に変更されました。
参考