セキュリティ
セキュアなAPIの設計
深刻なAPI侵害の大半は、暗号の破綻ではなく認可ロジックに起因します。本ガイドでは、オブジェクトレベルの認可、トークン設計、入力検証、レート制限、サービス間の信頼を取り上げ、それぞれの統制をどこに置くべきかを説明します。
· 5分で読めます · Starone エンジニアリング
APIはシステムのデータと操作に至る最も直接的な経路であり、ソフトウェアから呼び出されるように設計されているため、大量に探りを入れることが容易です。問題となる失敗が特殊なものであることはまれです。呼び出し元を認証したうえで他人のレコードを返してしまうエンドポイント、クライアントが設定すべきでないフィールドを受け付けるエンドポイント、高コストな操作を無制限に試行させてしまうエンドポイントです。
私たちの見解では、APIセキュリティは第一に設計の規律です。ゲートウェイやWebアプリケーションファイアウォールは有用ですが、どちらも、ユーザーAが請求書Bを読んでよいかどうかを知りません。その判断はアプリケーションの中にあり、リクエストのたびに行わなければなりません。
すべてのオブジェクトを、すべてのリクエストで認可する
オブジェクトレベル認可の不備(Broken Object Level Authorization)は、OWASP API Security Top 10の筆頭に挙げられ、古くからIDORとして知られてきた、APIを代表する脆弱性です。エンドポイントはトークンが有効であることを確認し、パスから識別子を取り出してオブジェクトを返しますが、そのオブジェクトが呼び出し元のものであるかは確認しません。URLの識別子を変えるだけで、別の顧客のデータが露出します。
UUIDのような推測困難な識別子は列挙を難しくしますが、統制にはなりません。識別子はログ、リファラー、他のレスポンスを通じて漏れるからです。対策は構造的なものです。すべてのデータアクセスを、クエリそのものの中で呼び出し元のアイデンティティとテナントによって絞り込み、その範囲外のレコードは単に見つからないようにします。マルチテナントシステムでは、データベースの行レベルセキュリティが、アプリケーションの下にもう一層の防御を加えます。
機能レベルの認可にも同じ注意が必要です。管理操作ではサーバー側でロールと権限を確認しなければならず、クライアントがボタンを隠していることに頼ってはなりません。私たちは認可ルールを、ミドルウェアまたはポリシーエンジンが評価するポリシーとして一元的に定義し、明示的なルールのないルートはすべてデフォルトで拒否します。
最小権限を前提にトークンを設計する
認証は、確立されたプロトコルであるOAuth 2.0とOpenID Connectを基盤とし、保守されているライブラリで実装すべきです。そのうえで残る設計判断は、盗まれたトークンでできることをどう制限するかに関するものです。
- アクセストークンは短命にし、リフレッシュトークンはローテーションさせ、再利用を検知したら失効させます。
- クライアントごとに狭いスコープを発行し、リソースサーバーで検査します。スコープでは代替できないオブジェクトレベルのチェックも併せて行います。
- リクエストのたびに署名、発行者、オーディエンス、有効期限を検証し、受け入れるアルゴリズムを固定して、トークン側がアルゴリズムを選べないようにします。
- ブラウザおよびモバイルクライアントにはPKCE付きの認可コードフローを使い、トークンをURLやログに含めません。
- 価値の高いAPIでは、DPoPまたは相互TLSバインディングによる送信者制約付きトークンを検討します。
境界で入力を検証し、プロパティを制御する
リクエストボディを永続化モデルに直接バインドすると、マスアサインメントが可能になります。クライアントがrole、balance、ownerIdといったフィールドを追加すると、フレームワークがそれを書き込んでしまいます。モデルをそのまま返せば逆の問題が生じ、インターフェースには決して表示されない内部フィールドが露出します。エンドポイントごとに明示的なリクエストスキーマとレスポンススキーマを使い、未知のフィールドは拒否し、権限、価格、所有権に影響するフィールドはすべてサーバー側で制御するものとして扱います。
すべてのリクエストは、ビジネスロジックが動く前に厳密なスキーマで検証すべきです。対象は型、形式、長さ、範囲、許容値です。ゲートウェイまたはミドルウェアで強制するOpenAPI定義は、契約を実行可能なものにし、不正な入力を早い段階で拒否します。検証はパラメータ化クエリと出力エンコーディングを補完するものであり、代替するものではありません。
Webhookの登録や画像のインポートのように、クライアントが指定したURLを取得するエンドポイントには、サーバーサイドリクエストフォージェリのリスクがあります。宛先を名前解決して許可リストと照合し、プライベートアドレスとメタデータ用のアドレス範囲をブロックし、内部サービスにアクセスできないネットワークセグメントから外向きの呼び出しを行います。
リクエストレートだけでなく消費量を制限する
レート制限は可用性を守り、クレデンシャルスタッフィングや列挙を遅らせます。認証済みのアイデンティティやAPIキーがある場合は、それをキーにすべきです。IP単位の制限は複数のアドレスに分散させることで容易に回避でき、共有ゲートウェイの背後にいるユーザーを不当に不利にするからです。
- 認証、パスワードリセット、ワンタイムコードのエンドポイントには、より厳しい制限を適用します。
- ページサイズ、ペイロードサイズ、アップロードサイズ、実行時間に上限を設けます。
- GraphQLでは、クエリの深さと複雑度に上限を設けます。一つのリクエストが数千回のリゾルバー呼び出しを引き起こしうるためです。
- SMSの送信や有料のサードパーティAPIの呼び出しなど、費用が発生する業務アクションを制限します。
- Retry-Afterヘッダーを付けて429を返し、行儀のよいクライアントがバックオフできるようにします。
内部ネットワークを信頼しない
よくあるアーキテクチャでは、エッジで認証を行い、内部サービスは到達したリクエストをすべて信頼します。その場合、一つのサービスが侵害されるか、SSRFの欠陥が一つあるだけで、システム全体を自由に動き回られてしまいます。サービスは、サービスメッシュまたはSPIFFEベースのアイデンティティ基盤が発行する短命なワークロード証明書を用いた相互TLSで互いを認証し、サービスアイデンティティに基づいて認可すべきです。
エンドユーザーのアイデンティティも一緒に伝える必要があります。呼び出し元サービスのアイデンティティしか見えない下流のサービスは、オブジェクトレベルのルールを強制できません。これが「混乱した代理人」問題です。ユーザーコンテキストは署名付きトークンで伝播させ、理想的にはホップごとに、オーディエンスをより狭めたトークンへ交換します。
公開しているものを把握し、監視する
ドキュメント化されていないエンドポイントは保護できません。API定義から生成したインベントリを維持し、古いバージョンは公表したスケジュールに従って廃止し、デバッグ用や内部用のルートは公開リスナーに載せません。認証と認可の判断は、呼び出し元、オブジェクト、結果とともにログに記録し、トークンと個人データはログから除外します。一つのアイデンティティからの403や404のレスポンスの割合が上昇することは、多くの場合、列挙の最も早い兆候です。
認可はテストも必要です。各エンドポイントを二人目のユーザーおよび二つ目のテナントとして呼び出し、アクセスが拒否されることをアサートする自動テストは、スキャナーが見逃すリグレッションを検出します。
釣り合いとトレードオフ
すべての統制がすべてのシステムに適しているわけではありません。サービス間の相互TLSは証明書のローテーションとデバッグのオーバーヘッドを伴います。プライベートネットワーク上にあり、サービスが少数の小さなシステムであれば、ネットワークポリシーと署名付きサービストークンが釣り合いの取れた出発点になりえます。静的なAPIキーは、スコープが限定され、ローテーション可能で、シークレットマネージャーに保管されているなら、リスクの低いサーバー間連携では許容できます。オブジェクトレベルの認可、厳密なスキーマ、消費量の制限は別です。攻撃者が最初に探す失敗に対処するものであるため、私たちはこれらを、規模にかかわらず譲れないものとして扱います。
