Security
7 min read
1427 views

マルチテナントリーク:"行レベルセキュリティ"がSaaSで失敗する時

IT
InstaTunnel Team
Published by our engineering team
マルチテナントリーク:"行レベルセキュリティ"がSaaSで失敗する時

Software-as-a-Service(SaaS)の世界では、”Data Bleed”ほど終わりのない災害はありません。これは業界の”Code Red”—マルチテナントの隔離が崩壊し、顧客Aがログインして顧客Bの機密財務記録、PII、戦略情報を見ることができる致命的な失敗です。

長年、開発者はRow-Level Security(RLS)を決定的な解決策と見なしてきました。シンプルな提案は:”各行にtenant_idをタグ付けし、データベースに任せるだけ”です。しかし、SaaSアーキテクチャが複雑になるにつれ、RLSだけに頼るのは危険な賭けになっています。

この深掘りでは、単なるBroken Object Level Authorization(BOLA)攻撃を超え、Connection Pool Contamination(コネクションプールの汚染)、Shared Cache Poisoning(共有キャッシュの毒化)、Async Context Leaks(非同期コンテキストのリーク)などのアーキテクチャの失敗例を探り、RLSが静かに失敗し、大規模なデータ漏洩を引き起こす原因を解説します。

1. 行レベルセキュリティの幻想

行レベルセキュリティ(RLS)は、PostgreSQL、SQL Server、Oracleなどのデータベース機能で、ユーザーのIDに基づき表示・操作できる行を制限するポリシーを定義できる仕組みです。

なぜそれが銀の弾丸のように感じるのか

一般的なRLS設定では、アプリケーションは次のように接続を確立し、コマンドを実行します:

SET app.current_tenant_id = 'customer_abc';

これ以降、SELECT * FROM invoicesは自動的にSELECT * FROM invoices WHERE tenant_id = 'customer_abc'となり、セキュリティロジックをアプリケーションコード(WHERE句を忘れる可能性のある場所)からデータベースエンジンに移行します。

致命的な欠陥:コンテキスト次第

RLSの根本的な問題は、ステートレスでありながら実務上はステートフルである点です。データベースは、「顧客A」が誰かをアプリケーションから教えられるまで知りません。アプリケーションとデータベースのセッションコンテキストの橋渡しが壊れると、セキュリティモデル全体が崩壊します。

最新の研究:CVE-2024-10976以降

最近の脆弱性では、RLSが完璧ではないことが示されています。CVE-2024-10976は、PostgreSQLにおいてサブクエリ以下の行セキュリティポリシーがユーザーIDの変更を無視できるシナリオを指摘しました。さらに、CVE-2025-8713は、最適化統計情報がRLSで隠すべき行のサンプルデータを漏らす可能性を明らかにしています。これらの”情報漏洩”は、クエリプランやエラーメッセージのサイドチャネル分析を通じて、攻撃者が他のテナントのデータ内容を推測できる危険性を孕んでいます。

2. 機械の幽霊:コネクションプールの汚染

現代のSaaSでは、すべてのリクエストごとに新しいデータベース接続を開くのは遅すぎます。そのため、Connection Pooling(例:PgBouncer、HikariCP、Prisma)を使用します。ここに最初の大きなアーキテクチャの失敗があります。

汚染はどう起こるのか

高トラフィックのSaaSアプリを想像してください。

  1. リクエスト1(テナントA)が到着。アプリはコネクション#42をプールから取得します。
  2. セッションコンテキストを設定:SET app.tenant_id = 'Tenant_A'
  3. クエリが実行され、データが返され、リクエストが完了します。
  4. 重大なエラー:例外やコーディングミスにより、アプリがコネクションを”クリーン”にしてプールに返さない場合。
  5. リクエスト2(テナントB)が到着。コネクション#42を取得します。
  6. アプリは新しい接続またはテナントIDの上書きを即座に行わないと仮定します。
  7. リークの発生:リクエスト2がSELECT * FROM secretsを実行し、コネクション#42がテナントAの状態のままの場合、データベースはテナントAの秘密情報をテナントBに提供します。

“クリーンアップ”失敗

多くの開発者は”ミドルウェア”に頼ってテナントIDを設定・リセットしますが、バックエンドサービスがクラッシュしたり、トランザクションが途中で中断された場合、”リセット”ロジックは実行されません。プールのプロキシ自体がRESET ALLDISCARD ALLコマンドを強制しない限り、コネクションは前のユーザの情報で”毒された”ままです。

3. 共有キャッシュの毒化:Redisがリークする時

ミリ秒未満のレイテンシを実現するために、SaaSアプリはRedisやMemcachedのような共有キャッシュに大きく依存します。これにより、二つ目の、しばしば見えないマルチテナントリスクが生まれます。

キースペースの衝突

最も一般的なキャッシュの失敗は、単純にtenant_idをプレフィックスに付け忘れることです。

  • 悪い例: GET user_profile_123
  • 良い例: GET tenant_A:user_profile_123

しかし、プレフィックスを付けても、アーキテクチャの”レース条件”が発生することがあります。

バックエンドのレース条件

“Cache-Aside”パターンを使ったシナリオを考えましょう。リクエストが来ると、アプリはRedisをチェックします。ミスの場合はDBに問い合わせ、結果をRedisに書き込みます。

  1. テナントAがダッシュボードをリクエスト。
  2. アプリはダッシュボードデータを計算しますが、非同期ロジックのバグにより、latest_dashboard_statsのような一般的なキーに書き込みます。
  3. ミリ秒後にテナントBがダッシュボードをリクエスト。キャッシュにヒットし、テナントAが書き込んだデータを受け取ります。

共有キャッシュの毒化(2025年の視点)

2024年・2025年には、新たなキャッシュリークのフロンティアが出現しました:マルチテナントLLMサービング。KV-Cacheの共有(例:”PROMPTPEEK”攻撃)に関する研究では、複数のユーザが効率化のためにGPUキャッシュを共有すると、一方のユーザがキャッシュヒットやタイミングサイドチャネルを分析して他方のプロンプトを再構築できることが示されています。これはAI特有の例ですが、広く言えば、最適化のために共有リソースを使うことは潜在的なリークの入口です。

4. 静かな殺し屋:非同期コンテキストのリーク

現代のSaaSバックエンドはほぼ完全に非同期(Node.js、Go、Python FastAPI)です。これらの言語は”コンテキスト”オブジェクトを使って、関数呼び出しのチェーンを通じてテナントIDを”prop drilling”せずに渡します。

シングルスレッドの罠

Node.jsでは、AsyncLocalStorageが標準的なテナント状態追跡方法です。しかし、開発者がグローバル変数やスコープの狭いシングルトンにtenant_idを保存すると、大きなデータリークリスクを生じさせます。

  • シナリオ: Node.jsは単一スレッド上で何千ものリクエストを処理します。
  • 失敗例:awaitブロック中に共有グローバル変数に誤って書き込みを行うと、その値を他のリクエストが引き継ぐ可能性があります。

バックエンドサービスのレース条件は、アイデンティティのスワッピングを引き起こすことがあります。リクエストAの”コンテキスト”が、リクエストBによって誤って上書きされるのです。これにより、セキュリティ上の重大な問題となります。

5. RLSを超えて:堅牢なマルチテナンシー戦略

RLSだけでは不十分な場合、何をすべきか?データリークを防ぐために、SaaSアーキテクトはディフェンス・イン・ディープを採用すべきです。

1. コネクションプールの”リセット”義務化

アプリケーションコードに依存せず、プールのプロキシ(例:PgBouncer)にserver_reset_queryを設定しましょう。コネクションがプールに返されるたびに、DISCARD ALLを実行し、一時テーブルやセッション変数、プリペアドステートメントをクリアします。

2. 暗号化による隔離(”ゴールドスタンダード”)

最終的な防御策は、たとえテナントが他のテナントのデータを見ることができても、それを読めないようにすることです。

アプリケーション層の暗号化(ALE): 機密性の高い列を、テナントごとに異なるキーで暗号化します。

もしテナントBが誤ってテナントAの行を取得した場合でも、暗号化されたままで、復号キーは別のKMS(例:AWS KMSやHashiCorp Vault)に保存されているため、内容は見えません。

3. ロジックレベルの二重チェック

データベースだけに頼らず、アプリケーションコードでも手動の検証を行います:

if record.tenant_id != current_user.tenant_id:
    raise SecurityLeakError("クロステナントアクセス検出!")

冗長に見えますが、多テナント環境では冗長性こそ安全への唯一の道です。

4. テナント認識キャッシュとACL

Redis 6.0以降を使っている場合は、単一の”admin”パスワードの使用をやめ、Redis ACLを利用してテナントごとのユーザを作成し、特定のキー範囲に制限しましょう:

ACL SETUSER tenant_A on >password ~tenant_A:* +get +set

キャッシュレベルでの隔離を徹底することで、アプリロジックのバグによる”クロステナントのGET”を防ぎます。

6. 結論:SaaSセキュリティのパラドックス

アーキテクチャの効率化(コネクションプール、共有キャッシュ、非同期処理)を進めるほど、マルチテナントリークのリスクは高まります。

Row-Level Securityは強力なツールですが、完全な解決策ではありません。それは”セーフティネット”であり、”要塞の壁”ではありません。本当のデータ隔離は、状態管理をセキュリティの最優先境界とする包括的なアプローチが必要です。

2026年に向けて、SaaSの複雑さは増す一方です。RLSの単一層に頼る組織は、やがて”Data Bleed”という恐怖に直面します。生き残るのは、データベースのコンテキストが失敗し、キャッシュが毒され、コネクションが汚染されることを前提にアーキテクチャを構築し、それに備えた組織です。

Continue from this article into the most relevant product guides and workflows.

Related Topics

#multi-tenant data leakage, saas data bleed, row level security failure, tenant isolation vulnerability, multi-tenant security risk, saas architecture flaws, connection pool contamination, shared cache poisoning, redis tenant leakage, memcached vulnerability, session data cross-tenant, backend race condition, data isolation failure, saas security breach, cross-tenant data exposure, multi-tenant misconfiguration, broken tenant isolation, cloud multi-tenant risk, database row level security flaws, saas authorization failure, data partitioning vulnerability, session mix-up attack, backend concurrency bug, distributed cache poisoning, cache key collision, tenant id injection, saas access control flaw, cross customer data leak, b2b saas security, enterprise saas breach, tenant boundary violation, microservices isolation failure, api multi-tenant security, identity scoping vulnerability, customer data exposure, saas data segregation, cloud application security risk, multi-tenant threat model, broken data tenancy, privilege escalation across tenants, backend state pollution, pooled connection vulnerability, web session contamination, authentication context leak, distributed systems security flaw, race condition exploit, isolation boundary failure, saas breach prevention, zero trust multi-tenancy, shared infrastructure risk, cloud data isolation, tenant aware architecture, authorization context bug, object ownership failure, saas backend vulnerability, application level data leak, cloud tenancy risk, devsecops saas, security architecture failure, cross-account access bug, backend design flaw, shared resource exploitation, saas data protection, multi-tenant audit controls, isolation testing saas, data residency breach

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles