blog

Spring Boot + Nginx でセキュリティ対策を大雑把に実施する

業務でセキュリティ診断を受けました。
数回の診断を経て、最終的には「指摘なし」との高評価をいただくことができました。
この記事では、高評価に至るまでに実施した事を残したいと思います。

なお、お約束ではありますが、セキュリティは要件次第で対策内容が変わるものです。
以下の内容は、大雑把にセキュリティ対応を行うためのもので、万全を保証するものではありません。


今回の構成

  • Spring Boot (3.4.1)
  • Spring Security
  • Thymeleaf
  • バニラJavaScript (react やvue などを使わない)
  • MyBatis
  • Nginx

Spring Boot

application-prd.yml に以下の設定を入れます。

YAML
server:
  servlet:
    session:
      cookie:
        secure: true   # (1)
        name: __Host-SESSION   # (2)

これらの設定はHTTPS 環境でないと動かないので、application-prd.yml に記載してapplication.yml (-prdなし)に記載しないと良いです。
こうすることでローカル環境でもSpring Boot に直接アクセスできます。

(1) Cookie中のセッションID をSecure 属性で保護します

付けなかった場合は、HTTP で通信を行った時にもcookie が送付されるため、セッションID が漏えいしてしまい、セッションを乗っ取られる可能性があります。
とても重要な設定です。

(2) Cookie中のセッションIDをホストオンリーとします

セッションIDの名前がデフォルトのJSESSIONID から変更されます。
__Host- が先頭につくことでサブドメインからのCookie 注入を防いでくれます。
こちらを設定した場合、Cookie にドメインを指定できなくなります。(する必要がない)


Spring Security

以下の設定を入れます。

Java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.headers((headers) -> headers
        .contentSecurityPolicy(config -> config
            .policyDirectives("default-src 'self'")
        ) // (3)
    ).csrf(csrf -> csrf
        .csrfTokenRepository(new HttpSessionCsrfTokenRepository())
    ); // (4)
    return http.build();
}

(3) Content security policy (CSP)の適用

CSP を適用することで、ブラウザに意図しない外部サイトのスクリプト実行を制限させます。
クロスサイトスクリプティング対策として有用です。
Nginx で設定することも可能ですが、開発時にSpring Boot へ直接アクセスすることがあるので、Spring Security で設定した方が良いと思います。加えて、nonce という例外を書く機能が使えるようになるメリットがありますが、私の場合は最後まで使うことはありませんでした。

設定した場合に、開発上の注意点が2つあります。

ひとつは、JS、CSS、画像が外部から読み込めなくなることです。
CDN など外部から読み込みが必要な場合は、例外の設定が必要になります。
私の場合、Google recaptcha が必要だったため、以下のような例外設定を追記しました。

default-src 'self’;
script-src 'self' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/;
frame-src https://www.google.com/recaptcha/ https://recaptcha.google.com/recaptcha/;
connect-src 'self' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/;

もうひとつは、onclick= やstyle= などのインライン属性が使えなくなります。
こちらはフロント側の開発に大きく影響します。

onclick についてはイベント形式に書き直します。

❌ CSP で制限される書き方

HTML
<span onclick="alert('クリックされた!')">クリック可能な文字列</span>

✅ CSP 対応済みの書き方

HTML
<span data-action="alert">クリック可能な文字列</span>
JavaScript
document.querySelectorAll('[data-action="alert"]').forEach(btn => {
  btn.addEventListener('click', () => {
    alert('クリックされた!')
  })
})

style= についてはCSS に必要なクラスを作成して対応します。

❌ CSP で制限される書き方

HTML
<span style="display:none">非表示</span>

✅ CSP 対応済みの書き方

HTML
<span class="app-display-none">クリック可能な文字列</span>
CSS
.app-display-none {
	display: none;
}

当初私は style が使えないことを面倒だと感じましたが、外観をCSS で全て管理できるので綺麗な書き方になると考え直しました。

(4) CSRF 対策

攻撃者がXSS などで任意のJavaScript を実行できる場合に、被害者のログイン済みサイトから送金操作などのデータ操作を行うことを防ぎます。
この設定を入れると、form タグにトークンが自動的に追加されます。

HTML
<form action="/action" method="post"> 
  <input type="hidden" name="_csrf" value="...長いトークン...">
  ...
</form>

2行目をSpring Security が自動的に挿入します。

同時に、Spring Securityがトークンの検証を行うため、意図しないページからのPOST アクセスを防ぐことができます。
注意点として、GETリクエストには適用されないことが挙げられます。
HTTP の原則に従って、GET でデータ操作を行わないというルールを守る必要があります。

バニラJavaScript(フレームワークを使わないJavaScript のこと)でサーバへリクエストを投げる(fetch)場合は、このトークンを付与することで投げることができます。
これは、攻撃者も同じことを行うことが可能なのですが、前述のCSP 対応と後述のThymeleaf によるXSS 対策で防ぐことになります。

今回の構成から外れますが、SPA の場合は、cookie を使った対策をフレームワークで行います。
私が経験のあるAngular について、リンクを置いておきます。


Nginx

以下の設定を入れます。

# (5)
server_tokens off;
# (6)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# (7)
add_header Referrer-Policy "same-origin, origin-when-cross-origin" always;

(5) Nginx のバージョンを表示しない

デフォルトではエラー画面やレスポンスヘッダーにNginx のバージョンが記載されます。
万一、使っているNginx のバージョンに脆弱性が見つかった場合、バージョン番号が攻撃者に有用な情報となってしまいます。

(6) Strict-Transport-Security ヘッダを設定

HTTPS を使っており、HTTP を使わないことをブラウザに覚えさせる設定です。
一度このヘッダーを受信したブラウザは、タイムアウトするまで(2年間)常にHTTPS でアクセスします。

(7) Referrer にパスを入れない

パスに大切な情報が含まれると情報漏洩につながる恐れがあるので、念の為外部サイトにパスを送付しない設定を入れます。

origin-when-cross-origin を指定すると、同一オリジンにはReferrer を送るが、クロスオリジンにパスを送付しなくなります。この機能は一部のブラウザで未対応らしです。
same-origin を指定すると、外部サイトにReferrer を送らなくなります。origin-when-cross-origin 未対応のブラウザはこちらが有効になります。


Thymeleaf

クロスサイトスクリプティング(XSS)対策として、文字列を代入するときに、th:text の使用を徹底します。

✅ 安全な書き方

HTML
<span th:text="${name}">ユーザ名</span>

th:utext を使うとHTML を代入することができますが、問題を起こす可能性が上がるので極力使いません。
要件次第ではありますが、私が携わったプロジェクトでは一切使わずに済みました。

❌ 問題の可能性が上がる書き方

HTML
<span th:utext="${html}">動的HTML</span>

その他のフレームワークを使っている場合でも、似たような機能があるはずです。


MyBatis

SQL インジェクション対策として、基本的には$ を使わず# を使うように徹底します。
しかし、order by だけは # で直接指定することができません。
代わりに<choose>を使います。

✅ 安全な書き方

SQL
SELECT
  id,
  name,
  email,
  created_at
FROM users
<choose>
  <when test="orderBy == 'name'">
    ORDER BY name
  </when>
  <when test="orderBy == 'createdAt'">
    ORDER BY created_at
  </when>
  <otherwise>
    ORDER BY id
  </otherwise>
</choose>

くれぐれもユーザが指定する値を直接$ で渡すことは避けてください。

❌ 問題の可能性が上がる書き方

SQL
SELECT ... FROM users ORDER BY ${orderBy}

その他

IPA の「安全なウェブサイトの作り方」を見ると、上で触れていない問題があります。

触れてはいませんが、構成上問題になりにくいものと、私の場合要件にないため問題にならないものがありましたので、分類しておきます。

上で触れたもの
1 SQLインジェクション
4 セッション管理の不備
5 クロスサイト・スクリプティング
6 CSRF(クロスサイト・リクエスト・フォージェリ)

構成上問題になりにくいもの
9 クリックジャッキング (Spring Security 使用のため)
10 バッファオーバーフロー (Java 使用のため)

要件にないので問題にならなかったもの
2 OSコマンド・インジェクション
3 パス名パラメータの未チェック/ディレクトリ・トラバーサル
7 HTTPヘッダ・インジェクション
8 メールヘッダ・インジェクション

こちらは今回の記事の対象外とさせてください。
11 アクセス制御や認可制御の欠落


感想

脆弱性診断で高評価を得られたことで、今回の記事を書く動機が湧きました。

勝因は、自前でセキュリティ対策のコードを書かずに、Spring、Nginx、Mybatis といった巨人に委ねた点だと考えています。

実装にあたって特に面倒だと感じたのは CSP、Thymeleaf、Mybatis のところでした。ですが、一度正しく設定して、雛形をチーム内で共有するところまでたどり着いた後は、スムーズに開発できたと思っています。チーム内で不満の声は上がらなかったです。

くどいですが、これらの対策を行えば万全というものではなく、何もしないよりこれらをやっておいた方が無難というものになります。
抜け道を作ることや設定を緩めることは可能ですが、その際は要件と設計とセキュリティをよく検討の上実施ください。

【広告】

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です