セキュリティ
データベースを認証に信頼しない理由
Why We Don't Trust the Database with Authentication (blog.sturdystatistics.com)
要約
API開発において、データベースを信頼の究極の源泉と見なす暗黙の前提は危険な脆弱性となり得ます。この記事では、データベースを認証の唯一の仲裁者と見なすことの危険性を説明し、SQLインジェクションがシステム全体へのアクセスを許可する可能性のあるバイパス攻撃のシナリオを示します。
全文翻訳
APIを開発する際、データベースを信頼の究極の源泉と見なすという危険な暗黙の前提を、知らず知らずのうちに導入してしまうことがあります。データベースにレコードが存在する場合、アプリケーションはそれを権威あるものとして扱うことがよくあります。この記事では、それがなぜ非常に悪い考えである可能性があるのかを説明したいと思います。
Sturdy Statisticsでは、私たちのコアエンジニアリング哲学は「多層防御(Defense in Depth)」です。私たちはあらゆるレイヤーでの失敗を防ぐために努力しますが、他のレイヤーが失敗する可能性を想定して各レイヤーを設計します。API認証に関しては、データベースをアイデンティティの唯一の仲裁者と見なすことは、標準的なプラクティスを装った重大な脆弱性です。なぜ私たちがデータベースを認証に信頼せず、データベースレベルの侵害がシステム全体への侵害になるのを構造的に防いでいるのかを説明します。
バイパス:SQLインジェクションがシステム全体へのアクセスを許可する場合
APIキーの処理における危険な暗黙のデフォルトを考えてみましょう。マシン・トークンをユーザー・パスワードのように扱う場合、実装は明白に見えます:ランダムなシークレットを生成します。それをハッシュ化します(例:SHA-256)。ハッシュをapi_keysテーブルにorg_idと共に格納します。リクエストが来たら、提供されたシークレットをハッシュ化し、一致する行を探します。データベースにはプレーンテキストのシークレットではなくハッシュのみが格納されているため、これは安全に思えます。しかし、攻撃者がアプリケーションのどこかでブラインドSQLインジェクション脆弱性を発見した場合に何が起こるかを見てみましょう。
攻撃者はデータベースを読み取ったり、ハッシュを逆転させたりする必要はありません。攻撃者は単に正規のアカウントを登録し、独自の有効なAPIキーを生成できます。キーは有効であり、合法的に攻撃者のものであるため、攻撃者はプレーンテキストのシークレットを知っており、アプリケーションがそれを正常に検証することを知っています。ここまでは、完全に合法です。
ここで、攻撃者がSQLインジェクションのエクスプロイトを見つけたと想像してみましょう。単純なUPDATEステートメントを実行することで、彼は自分のキーのハッシュをコピーして、被害者のキーのハッシュに上書きできます。攻撃者が被害者のデータにアクセスしようとするリクエストを送信するとき、彼は自分の(有効な)キーを使用します。APIミドルウェアはそれをハッシュ化し、被害者の行(現在は攻撃者が貼り付けたハッシュが含まれている)を見つけ、アクセスを許可します。ブーム。これで、他のセキュリティ対策がどれだけ多くても関係ありません。攻撃者はAPIセキュリティ境界とテナント境界レイヤー全体をバイパスしました。彼の攻撃は有効なキーを使用します。アプリケーションがデータベースのハッシュレコードを盲目的に信頼しているため、単純なデータベース書き込みが完全なテナント乗っ取りに変換されました。
修正:暗号学的バインディング
SQLインジェクションからデータベースを保護することは明らかに優先事項ですが、この攻撃の「バイパス」という性質は、ペリメーターに適用するものと同等の多層防御が必要であることを意味します。認証システムのセキュリティとデータベースのセキュリティを分離する必要があります。これを達成するために、Sturdy Statisticsは単純なAPIキーのハッシュを格納しません。代わりに、サーバーサイドの暗号学的ペッパー(バックエンドにTPMでシールされた高エントロピーのシークレット)を使用してHMAC-SHA512署名を計算します。重要なのは、シークレットを署名するだけではないということです。キーの構造的コンテキストを署名します:
Stored Hash = HMAC-SHA512(Pepper, api-key-id || rotation-version || org-id || secret)
これらのピースはどこから来るのでしょうか?api-key-id、rotation-version、およびsecretは、受信したクライアントトークンから直接解析されます。org-idは、要求されたURLパスパラメータから厳密に取得されます。データベースは、照合するための格納済みハッシュと、api-key-id、rotation-version、およびorg-idのレコードを提供します。したがって、シークレットと格納済みハッシュはそれぞれ一度だけ表示されます。他の識別子はそれぞれ2つの場所に格納されます。この「二重簿記」により、一貫性をチェックできます。
攻撃者がデータベースへの書き込みに成功し、組織Aのハッシュを組織Bの行に上書きした場合、攻撃は即座に失敗します。私たちの認証ミドルウェアが受信したトークンを検証するとき、URLパスから組織Bのorg-idを取得し、それをHMAC計算に注入します。ハッシュは間違ったorg-idをエンコードしているため、ハッシュが変更され、署名が拒否され、攻撃者はロックアウトされます。
重要なのは、この署名にはPepperが必要であり、Pepperはバックエンドメモリにのみ存在し、データベースには決して触れないため、データベースの読み書きアクセス権を完全に持つ攻撃者でさえ阻止されることです。Pepperなしでは、キーを作成または変更することは不可能です。データベースはもはや単独でアイデンティティを決定しません。バックエンドのみが検証できる検証者を格納します。したがって、バックエンドの防御レイヤーはそのまま維持され、DBを介してバイパスされることはありません。
ロールバック耐性、またはゾンビキー
なぜハッシュにrotation-versionも含まれているのか疑問に思うかもしれません。これは、キーのライフサイクルの特定の época に署名をバインドします。これは、ロールバック攻撃に対する防御の最初の半分です。キーが侵害され、最終的にローテーションされた場合、古いV1ハッシュは無効になります。しかし、DBアクセス権を持つ攻撃者が、アクティブな行を上書きし、ハッシュをV1ハッシュに戻し、rotation-versionを1に戻すことで、その取り消されたキーを復活させようとしたらどうなるでしょうか?HMACはV1ハッシュがV2キーとして通用するのを防ぐため、攻撃者はハッシュ計算を機能させるためにバージョン列をロールバックする必要があります。私たちは、暗号学的署名とデータベーストリガーをペアにすることでこれを停止します。データベースエンジンを、rotation_version列に対して厳密な一方向ラチェットを強制するように設定します。それは常に増加することしかできません。攻撃者がバージョンを元に戻すUPDATEステートメントを試みると、データベーススキーマ自体がトランザクションを拒否します。
この組み合わせにより、DBへの書き込みアクセス権と被害者の組織からの期限切れキーへのアクセス権を持つ攻撃者でさえロックアウトされます。
偏執的なテナンシー:4層の検証
APIキーをテナントに暗号学的にバインドすることは最初のステップですが、多層防御には重複するセキュリティ境界が必要です。マルチテナント環境では、クロステナントのデータ漏洩は、OWASPによって特定された最も一般的なセキュリティ障害です。絶対的な分離を保証するために、すべてのリクエストのライフサイクルの全期間にわたってテナンシーチェックを繰り返し実施します:
ルーティングレイヤー:テナンシーは、リクエストパス自体(パスパラメータとして)で明示的に宣言されており、ルーティングインフラストラクチャは、アプリケーションコードが実行される前に、どの論理境界にアクセスされているかを正確に認識します。
認証レイヤー:上記で詳述したように、APIキー検証はorg_idに直接暗号学的にリンクされています。
アプリケーションレイヤー:アプリケーションレベルのすべての読み書き操作は、テナントスコープを強制します。ビジネスロジック関数は、生の識別子を受け入れません。それらは、処理を進めるために承認されたテナントコンテキストを必要とします。
データベーススキーマレイヤー:最終的なフェイルセーフとして、データベーストリガーを使用して、可能な限り低いレベルでテナンシーを強制します。アプリケーションコードに論理エラーがあってもクロステナント書き込みを試みても、スキーマはそれを拒否します。
優雅なセキュリティ:ゼロダウンタイムローテーション
使いにくいセキュリティメカニズムは、通常、バイパスされることになります。キーローテーションは悪名高く困難であり、APIプロバイダーとクライアントの間で調整されたダウンタイムを必要とすることがよくあります。ハッシュとデータベーススキーマの正確なメカニクスを明示的に制御しているため、ゼロダウンタイムローテーションをアーキテクチャに直接組み込みました。api_keysテーブルには、prev-hash列とgrace-period-expires-atタイムスタンプが含まれています。ユーザーがキーをローテーションすると、新しいシークレットを生成し、ローテーションバージョンをインクリメントし、新しいプライマリハッシュを計算します。古いハッシュはprev-hash列に降格され、厳密な有効期限(例:24時間)が与えられます。このウィンドウ中、バックエンドチェックはどちらのキーも承認します。これにより、分散クライアントシステムは、単一の合法的なリクエストをドロップすることなく、非同期に環境変数を更新できます。
注意している場合、これはロールバックの脆弱性を再導入します。DB書き込みアクセス権と期限切れキーへのアクセス権を持つ攻撃者は、猶予期間を上書きすることでキーを復活させることができます。私たちはこれを別のトリガーで解決します。猶予期間の ca