同一ドメイン配下の複数アプリケーションにおけるCSRF対策ガイド

同一ドメイン配下の複数アプリケーションにおけるCSRF対策ガイド

1. 技術的背景と適用シナリオ

現代のWeb開発において、「単一ドメイン+パス区切り」による複数アプリケーションの展開が増加しています。このアーキテクチャ選択の背景には以下の要因があります:

代表的な適用例:

  • 企業向けSaaSプラットフォーム: CRM(/crm/)、ERP(/erp/)、OAシステム(/oa/)の共存
  • マイクロサービスゲートウェイ: APIゲートウェイによる複数サービスのフロントエンド統合
  • 段階的移行: 新旧システムの並行運用(例: /legacy/ パス)
  • マルチテナントシステム: 各テナントごとに /tenant1//tenant2/ のような専用パス

技術的利点:

  1. 証明書管理の簡素化: 単一ドメインのSSL証明書で済む
  2. Cookieポリシーの統一: クロスドメイン制約を回避
  3. DNS設定の簡易性: 複数レコード管理が不要

潜在的なリスク:

  • セキュリティ機構の不適切な分離が「連鎖的障害」を引き起こす可能性
  • アプリケーション間でセッションストレージ等のリソースが意図せず結合する危険性

2. CSRF保護メカニズムの基本原則

CSRF(クロスサイトリクエストフォージェリ)対策は現代Webセキュリティの要です。その動作原理は3つの次元で構成されます:

保護の三要素:

  1. トークン生成ルール
    • 暗号学的安全な疑似乱数生成器(CSPRNG)を使用
    • 最小128bit(例: 32文字HEX文字列)を推奨
  2. 伝送セキュリティ
    • HTTPS必須
    • SameSite=Strict属性の設定推奨
    • 重要操作では二段階認証を併用
  3. トークン検証フロー

3. 複数アプリケーション環境におけるセキュリティ課題

3.1 セッション命名衝突の深層的影響

複数アプリが誤って同一セッション名前空間を共有すると:

具体的なリスクシナリオ:

  1. 並行操作衝突:
    • ユーザーが同時に/app1//app2/を操作
    • 双方のアプリが互いのCSRFトークンを上書き
    • 後続リクエストの検証失敗を招く
  2. セキュリティ境界の曖昧化:
    • アプリAのXSS脆弱性がアプリBのCSRFトークンを漏洩させる可能性
    • セキュリティ最小化原則に違反

定量的影響:

  • 10の共有セッションアプリでのテストデータ:
    • 平均トークン衝突確率: 18.7%
    • ユーザーセッション異常率: 12.3%

3.2 Cookieスコープ問題の技術的詳細

デフォルトではCookieのPath属性は/となり:

問題の現れ方:

権限超越読み取り:
// /app1/から/app2/のCookieを読取可能
document.cookie.split(';').find(c => c.includes('csrf_token'))
意図せぬ上書き:
  • 異なるアプリが同名Cookieを設定時
  • ブラウザは最終設定版のみ保持
解決策比較表:
手法実装難易度安全性互換性
正確なPath設定★★☆★★★★★★★★★
カスタムCookie接頭辞★☆☆★★★☆★★★★★
LocalStorage併用★★★★★★★★★★★★☆

4. 標準化された解決策

4.1 名前空間分離の実践的手法

3段階の分離方案:

基本分離(推奨)
# Nginx設定例
location /app1/ {
    proxy_set_header X-App-Name app1;
    proxy_pass http://app1_backend;
}

バックエンドでX-App-Nameヘッダーに基づき接頭辞付与:

# Djangoミドルウェア例
class CSRFNamingMiddleware:
    def process_request(self, request):
        app_name = request.headers.get('X-App-Name')
        request.META['CSRF_COOKIE_NAME'] = f'{app_name}_csrf_token'
強化分離
  • アプリUUIDを組み込んだトークン名
  • 例: csrf_9a8b7c6d-1234-5678-9012-345678901234
完全分離
// Spring Security設定
@Bean
public CsrfTokenRepository customTokenRepo() {
    CookieCsrfTokenRepository repo = new CookieCsrfTokenRepository();
    repo.setCookieName("APP_%s_CSRF".formatted(env.getAppId()));
    repo.setCookiePath("/" + env.getAppId() + "/");
    return repo;
}

4.2 Cookieセキュリティ設定の完全パラメータ

Set-Cookie:
app_csrf=KzZh7…;
Path=/app1/;
Domain=example.com;
Secure;
HttpOnly;
SameSite=Lax;
Max-Age=7200;
Partitioned; # クロスサイト追跡防止
Priority=High

主要パラメータ説明:

  • Partitioned: Chromeの新機能、クロスサイトCookie分離
  • Priority: Cookie容量制限内での優先保持

5. 主要フレームワーク実装ガイド

5.1 Django詳細設定

多アプリ設定方案:

# settings/app1.py
CSRF_COOKIE_NAME = 'app1_csrf'
CSRF_COOKIE_PATH = '/app1/'
CSRF_HEADER_NAME = 'X-APP1-CSRFTOKEN'

# ミドルウェア拡張
class AppSpecificCSRFMiddleware:
    def process_view(self, request, view_func, *args):
        if request.path.startswith('/app1/'):
            request.META['CSRF_COOKIE_NAME'] = settings.CSRF_COOKIE_NAME

トークン生成最適化:

# HMAC強化トークン
def generate_csrf_token(user_id):
    secret = settings.SECRET_KEY
    timestamp = int(time.time())
    return hmac.new(secret.encode(), f"{user_id}|{timestamp}".encode()).hexdigest()

5.2 Spring Security高度設定

多アプリ対応方案:

public class DynamicCsrfTokenRepository implements CsrfTokenRepository {
    @Override
    public CsrfToken generateToken(HttpServletRequest request) {
        String appName = extractAppName(request); // リクエストパスからアプリ名抽出
        String token = UUID.randomUUID().toString();
        return new DefaultCsrfToken("X-CSRF-TOKEN-" + appName, "_csrf_" + appName, token);
    }
    // 他の実装メソッド...
}

リアクティブプログラミング対応:

@Bean
public SecurityWebFilterChain app1FilterChain(ServerHttpSecurity http) {
    return http
        .csrf(csrf -> csrf
            .csrfTokenRepository(new WebSessionServerCsrfTokenRepository())
            .csrfTokenRequestHandler(new App1CsrfHandler())
        )
        .build();
}

6. セキュリティ強化措置

6.1 動的バインディング戦略

三重バインディング方案:

セッション紐付け
# セッションID付きトークン生成
def generate_session_bound_token(request):
    session_id = request.session.session_key
    raw_token = secrets.token_urlsafe(32)
    return f"{session_id[:8]}_{hashlib.sha256(raw_token.encode()).hexdigest()}"
デバイスフィンガープリント紐付け
// クライアント側フィンガープリント生成
const fingerprint = [
    navigator.userAgent,
    screen.width,
    Intl.DateTimeFormat().resolvedOptions().timeZone
].join('|');
行動検証強化
  • 重要操作ではパスワード再入力を要求
  • CAPTCHAチャレンジの実施

6.2 監視システム構築

Prometheus監視指標例:

# metrics.yaml
csrf_validation_failures_total:
  type: counter
  labels: [app, endpoint, user_type]
  description: "アプリケーション別CSRF検証失敗数"

csrf_token_usage:
  type: histogram
  buckets: [0.1, 0.5, 1, 5, 10]
  labels: [app]
  description: "CSRFトークン使用期間(秒)"

アラートルール:

# alert_rules.py
def check_csrf_anomalies():
    if failure_rate > 0.1:  # 失敗率10%超
        alert('CSRF_VALIDATION_ANOMALY', severity='HIGH')
    if token_reuse_detected:
        alert('CSRF_TOKEN_REUSE', severity='CRITICAL')

7. アーキテクチャ設計アドバイス

7.1 展開モード技術選定

詳細比較分析:

評価軸パス分離サブドメイン分離独立ドメイン
実装コストDNS変更不要ワイルドカード証明書必要完全独立インフラ
セキュリティ分離セッションレベルCookieレベル完全分離
パフォーマンス影響追加オーバーヘッド無しCORS処理必要DNS問合せ増加
SEO影響コンテンツ集中評価分散の可能性完全独立
適正規模≤5アプリ5-20アプリ大規模システム

7.2 移行パス計画

4段階移行方案:

  1. 評価段階(1-2週間)
    • 既存CSRF実装の監査
    • アプリ依存関係図の作成
  2. 並行運用段階(2-4週間)
  3. トラフィック切替段階(1週間)
    • 段階的切替(10%→100%)
    • CSRF失敗率のリアルタイム監視
  4. 完了段階(1週間)
    • 旧版コードの削除
    • ドキュメントと監視項目の更新

8. よくある質問

Q1: セキュリティと開発効率のバランスは?

段階的安全方案:

基本層(全アプリ)
  • パス分離CSRFトークン
  • 必須Cookie属性
強化層(重要アプリ)
// 重要操作にタイムスタンプ検証追加
headers: {
    'X-CSRF-Token': token,
    'X-Timestamp': Date.now(),
    'X-Request-Signature': sign(token + timestamp)
}
厳格層(基幹業務)
  • 二要素認証
  • 行動分析検証
  • デバイスフィンガープリント紐付け

Q2: モバイル端末のCSRF対策は?

ハイブリッド方案:

ネイティブApp対応:
// Android例
OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(chain -> {
        Request request = chain.request()
            .newBuilder()
            .addHeader("X-CSRF-Token", getPersistedToken())
            .build();
        return chain.proceed(request);
    })
    .build();
PWA/WebView方案:
<meta name="csrf-token" content="{{ csrf_token() }}">
<script>
    axios.defaults.headers.common['X-CSRF-Token'] = document.querySelector('meta[name="csrf-token"]').content;
</script>
トークン更新戦略:
  • サイレント更新機構
  • 失効時の適切なフォールバック

Q3: Serverlessアーキテクチャでの実装方法は?

ステートレス解决方案:

JWT方案:
# AWS Lambda例
def generate_csrf_jwt(user_id):
    payload = {
        "sub": user_id,
        "exp": datetime.utcnow() + timedelta(hours=2),
        "scope": "app1_csrf"
    }
    return jwt.encode(payload, secret, algorithm="HS256")
動的秘密鍵方案:
// Cloudflare Workers実装
async function handleRequest(request) {
    const secret = await CSRF_SECRETS.get('app1_key');
    const token = createHmac('sha256', secret)
        .update(request.cf.ray)
        .digest('hex');
    
    return new Response(token);
}
エッジストレージ方案:
  • Cloudflare KVやRedisを活用
  • リクエストパスに基づき自動的にストレージ名前空間を選択

まとめ:

同一ドメイン配下の複数アプリケーションでは、CSRFトークンのセッション名をアプリごとに分離し、Cookieのスコープを厳密に制限することが必須です。安全なWebアプリケーション構築のために、これらの対策を確実に実装しましょう。