CyberFix Note
セキュアコーディング

APIセキュリティの基本。認証認可・レート制限・入力検証とOWASP API Top 10で守る

対象の目安: Webアプリ開発者 / 実務レベル

ソウ攻撃・脆弱性リサーチ担当
・ 約16分で読めます
APIセキュリティの基本。認証認可・レート制限・入力検証とOWASP API Top 10で守る

Web APIは、いまやアプリケーションの中心にあります。SPAやモバイルアプリの裏側、マイクロサービス間の通信、外部パートナーとの連携、そのほとんどがHTTP上のAPIでやり取りされています。画面というクッションを挟まずデータと処理を直接公開する分、APIは攻撃者にとって扱いやすい入口にもなります。ブラウザのボタンを隠しても、APIエンドポイントは誰でも直接叩けるからです。

ところが、画面(フロントエンド)を前提にしたセキュリティの感覚をそのままAPIに持ち込むと、典型的な穴を作り込みます。「管理者しか見えない画面だから安全」「フロントで入力チェックしているから安全」という発想は、APIの世界では通用しません。リクエストはブラウザを経由せず、改ざんされ、想定外の順序で、想定外の量で飛んできます。

この記事では、APIセキュリティの土台となる三本柱、認証認可・レート制限(リソース消費の制御)・入力検証を、原理と「どういう条件で破れるのか」から解説します。あわせて、業界の共通言語であるOWASP API Security Top 10(2023年版)を軸に、開発者がリクエストごとに何を検証すべきかを実務目線で整理します。

APIセキュリティの三本柱と全体像

まず全体像を早見表で示します。どの対策が「何を守るためのものか」を取り違えると、対策の優先順位を誤ります。

守る対象代表的な失敗対策の方向性
認証(Authentication)なりすましの防止トークンの検証不備、弱い発行・失効管理標準的なトークン検証、失効の仕組み
認可(Authorization)権限のないアクセスIDを書き換えると他人のデータが見える(BOLA)リクエストごとにサーバー側で所有権・権限を検証
リソース消費の制御可用性・コストレート制限なしで大量リクエストやコストを誘発レート制限、ページング上限、サイズ制限
入力検証不正データ・インジェクションフロント任せ、過剰なプロパティの受け入れサーバー側で許可リスト検証、出力時のエスケープ

この4つは独立しているようで連動します。たとえば認可が甘いと、レート制限をかいくぐった攻撃者が他人のデータを大量に収集できます。入力検証が甘いと、本来は更新できないはずのフィールドが認可をすり抜けて書き換えられます。個別対策の積み上げではなく、リクエスト1本ごとに「誰が・何に・どれだけ・どんな形で」アクセスしているかを検証する、という一貫した姿勢が要になります。

認可の不備(BOLA)がなぜAPIで警戒されるのか

OWASP API Security Top 10(2023)の第1位は API1:2023 Broken Object Level Authorization(オブジェクトレベル認可の不備、BOLA) です。OWASPはこのリスクの蔓延度を「Widespread(広範)」とし、API実装で「extremely common(きわめてよく見られる)」と説明しています。なお2023年版のMethodologyでは、頻度の統計分析に足る応募データは得られなかったとも明記されており、「最頻出」と数値で断定するより「最上位に置かれた広範なリスク」と捉えるのが正確です。

仕組みは単純です。たとえば GET /api/orders/1024 という注文取得APIがあるとします。ログイン自体は正しく行われていて、トークンも有効です。しかしサーバー側で「この注文1024は、いまリクエストしているユーザーのものか」を確認していなければ、攻撃者はIDを 1025 1026 と順に書き換えるだけで他人の注文を次々に読み出せます。認証は通っているのに認可が抜けている、という状態です。

ここがつまずきやすい点です。認証(ログインできているか)と認可(その人がそのデータを扱ってよいか)は別の問題です。多くの実装は「ログインしていること」までは丁寧に確認しますが、「このオブジェクトの所有者か」の確認を忘れます。とくにURLやリクエストボディにIDが含まれる設計では、そのIDが本人のものかをサーバーが毎回照合しなければなりません。

# 悪い例: 認証は通っているが所有権を確認していない
@app.get("/api/orders/{order_id}")
def get_order(order_id: int, user=Depends(current_user)):
    return db.get_order(order_id)  # 他人のorder_idでも返ってしまう

# 良い例: 取得後にリクエスト元の所有物か検証する
@app.get("/api/orders/{order_id}")
def get_order(order_id: int, user=Depends(current_user)):
    order = db.get_order(order_id)
    if order is None or order.user_id != user.id:
        raise HTTPException(status_code=404)  # 存在を漏らさないため404
    return order

誤解しやすいのは「推測されにくいIDにすれば安全」という考えです。UUIDなど推測困難なIDは総当たりを難しくしますが、認可そのものの代わりにはなりません。IDがログやリファラ、共有URLから漏れる経路はいくらでもあります。IDの推測困難性は補助であって、認可の検証を省く理由にはなりません。

注意

権限のないオブジェクトへのアクセスに対しては、403(Forbidden)ではなく404(Not Found)を返す設計も検討してください。403は「そのIDは存在するが権限がない」ことを攻撃者に教えてしまい、IDの存在確認(列挙)の手がかりになります。ただしAPIの性質によっては明示的に403を返す方が適切な場合もあるため、仕様として一貫させることが重要です。

関連して API5:2023 Broken Function Level Authorization(機能レベル認可の不備) もあります。これはオブジェクトではなく「機能」の認可漏れで、一般ユーザーが管理者用エンドポイント(例: DELETE /api/users/{id})を直接叩けてしまうケースです。BOLAが「他人のデータ」、機能レベルが「使ってはいけない操作」と整理すると区別しやすくなります。OWASP Top 10全体の位置づけは

でも扱っています。

認証の落とし穴と、プロパティ単位の認可

API2:2023 Broken Authentication(認証の不備) は、トークンの発行・検証・失効のどこかに穴があるケースです。よくあるのは、JWT(JSON Web Token)の署名検証を正しく行っていない、有効期限(exp)や発行者(iss)・受信者(aud)などの標準クレームを検証していない、ログアウトしてもトークンが失効されず使い続けられる、といったものです。

OWASPのREST Security Cheat Sheetは、JWTについて「署名またはMACで完全性を保護し、署名なしのJWT(alg: none)を許可しないこと」を明記しています。さらに、発行者・受信者・有効期限・有効化前(nbf)といった標準クレームを必ず検証し、明示的なログアウト時にはトークン拒否リスト(denylist)で無効化することを推奨しています。トークンを「持っているだけで信用する」のではなく、毎回中身を検証する姿勢が前提です。

もう一つ、見落とされやすいのが API3:2023 Broken Object Property Level Authorization(オブジェクトプロパティレベル認可の不備) です。これは2023年版で、旧版の「過剰なデータ露出」と「マスアサインメント」を統合した項目です。2つの方向の問題を含みます。

  • 過剰な露出(読み取り側): APIがオブジェクト全体をそのまま返してしまい、本来見せるべきでないフィールド(内部ID、権限フラグ、他人の個人情報など)まで応答に含まれる。
  • マスアサインメント(書き込み側): リクエストボディをそのままオブジェクトに割り当ててしまい、ユーザーが本来更新できないフィールド(isAdmin role balanceなど)を上書きできてしまう。
// 攻撃例: プロフィール更新APIに本来許されないフィールドを混ぜる
PATCH /api/users/me
{ "displayName": "taro", "isAdmin": true }

対策は、入出力のフィールドを明示的に定義することです。受け取る側は許可したフィールドだけを取り込む(許可リスト方式)。返す側は必要なフィールドだけを詰めたレスポンス用の型(DTO)を経由させ、エンティティをそのまま返さない。「便利だからモデルを丸ごと受け渡す」設計が、このリスクの温床になります。

レート制限とリソース消費の制御

API4:2023 Unrestricted Resource Consumption(無制限なリソース消費) は、旧版の「リソース不足とレート制限の欠如」を改称・拡張した項目です。守る対象はデータの機密性ではなく、可用性とコストです。

レート制限がないと、攻撃者は1つのエンドポイントに大量のリクエストを送り、サーバーを過負荷にできます。さらに現代では、リクエスト1本がメール送信・SMS送信・課金処理・外部API呼び出しといった「お金がかかる処理」を引き起こすことがあります。レート制限の欠如は、サービス停止だけでなく、想定外のコスト(クラウド従量課金やSMS料金)という形でも被害を生みます。

OWASPのREST Security Cheat Sheetは、リクエストが速すぎる場合に 429 Too Many Requests を返すこと、過大なリクエストボディには 413 で拒否すること、APIキーで利用量を管理することを挙げています。実務では以下を組み合わせます。

  1. 1

    レート制限を多層でかける

    IPやユーザー、APIキー単位でリクエスト頻度に上限を設けます。認証前(ログインAPIなど)は総当たり対策として特に厳しめに、認証後は利用実態に合わせて設定します。

  2. 2

    ペイロードとページングの上限を決める

    リクエストボディのサイズ上限、配列の要素数上限、ページングの1ページ最大件数(例: 100件)を必ず設けます。limitを無制限にできると、1回のクエリでデータベースを枯渇させられます。

  3. 3

    高コスト処理に追加のしきい値を置く

    メール・SMS・外部課金など費用が発生する処理は、通常のレート制限とは別に「1ユーザーあたり1日N回まで」のような上限を設けます。

  4. 4

    タイムアウトと同時実行数を制限する

    1リクエストあたりの処理時間や、重い処理の同時実行数に上限を設け、1つのエンドポイントが全体を巻き込まないようにします。

メモ

レート制限は「攻撃を完全に防ぐ」ものではなく「攻撃の効率とコストを下げ、検知の時間を稼ぐ」ものです。正規ユーザーへの影響を避けるため、まずは緩めに監視ログを取り、実際のトラフィックを見ながら段階的に締めるのが現実的です。429を返す際は、いつ再試行できるかを示す Retry-After ヘッダを添えると親切です。

関連して API6:2023 Unrestricted Access to Sensitive Business Flows(重要なビジネスフローへの無制限アクセス) も2023年版の新項目です。これは単なる頻度の問題ではなく、「予約の買い占め」「キャンペーンの不正な大量応募」のように、自動化されたアクセスでビジネス上の流れが悪用されるリスクです。技術的なレート制限だけでなく、ビジネスフローの観点からどこを自動化から守るべきかを設計時に考える必要があります。

入力検証は「サーバー側が本番」

フロントエンドの入力チェックは、あくまでユーザー体験(即時のフィードバック)のためのものです。セキュリティ境界にはなりません。攻撃者はブラウザを使わず、curlやスクリプトで直接APIを叩けるからです。入力検証はサーバー側で必ず行う、これが大前提です。

検証の基本は許可リスト(allowlist)方式です。「危ないものを弾く」拒否リスト方式は、想定外の入力を取りこぼします。代わりに「許可する形・型・範囲だけを通す」方針にします。

  • 型: 数値であるべき項目に文字列が来たら拒否する。強い型付けやスキーマ検証を使う。
  • 長さ・範囲: 文字列長、数値の最小・最大、配列の要素数に上限を設ける。
  • 形式: メールアドレスやIDなど、形式が決まっているものは正規表現などで検証する。
  • 必須・任意: 欠けてはいけない項目、来てはいけない項目を明示する。

入力検証は、同時にインジェクション対策の入口にもなります。ただし入力検証だけでインジェクションを完全に防ぐことはできません。SQLインジェクションならプレースホルダによる命令と値の分離、出力時にはコンテキストに応じたエスケープ、という出口側の対策と組み合わせる必要があります。IPAの「安全なウェブサイトの作り方」も、SQLインジェクションやクロスサイト・スクリプティングなどを、入力だけでなく実装全体の問題として整理しています。

フロントで丁寧にバリデーションを実装していたので安心していたが、ペネトレーションテストで「APIを直接叩けば全部素通り」と指摘された。検証ロジックがフロントにしかなく、サーバー側は受け取った値をそのまま信じていた。

あるAPI開発チームの振り返り(一般化した例)

これは非常によくある失敗です。バリデーションのコードを共有したい気持ちはわかりますが、サーバー側の検証は省略できません。フロントとサーバーで二重になるのは無駄ではなく、役割が違うのです。フロントは体験のため、サーバーは安全のためです。

設計・運用に効く残りの項目

OWASP API Security Top 10(2023)の残りの項目も、設計と運用の観点で重要です。

  • API7:2023 Server Side Request Forgery(SSRF): ユーザーが指定したURLをサーバーが取りに行く機能(画像取得、Webhook、URLプレビューなど)で、攻撃者が内部ネットワークやクラウドのメタデータエンドポイントを叩かせる攻撃です。取得先URLを許可リストで制限し、内部アドレスへのアクセスを遮断します。
  • API8:2023 Security Misconfiguration(セキュリティ設定ミス): 不要なHTTPメソッドの開放、過剰なCORS許可、詳細なエラーメッセージの露出などです。本番では詳細なスタックトレースを返さない、CORSは必要なオリジンだけ許可する、といった締めが必要です。
  • API9:2023 Improper Inventory Management(不適切な資産管理): 古いバージョンのAPI(/v1)や、ドキュメントに載っていない「野良エンドポイント」、検証用に公開したままの環境が放置されるリスクです。どのAPIがどこで動いているかの棚卸し(インベントリ)が対策の土台です。
  • API10:2023 Unsafe Consumption of APIs(APIの安全でない利用): 自分が呼び出す側、つまりサードパーティAPIからの応答を無条件に信頼してしまうリスクです。外部からのレスポンスも入力とみなして検証します。

これらは「コードのバグ」というより設計・運用の問題が多く、コードレビューだけでは見つかりません。APIの一覧管理、設定の定期点検、外部連携先の信頼境界の整理といった、継続的な運用の仕組みが必要です。

よくある質問

OWASP API Security Top 10と、通常のOWASP Top 10は何が違いますか?
通常のOWASP Top 10はWebアプリ全般の代表的リスク、API Security Top 10はAPI特有のリスクに特化したものです。とくにオブジェクト単位の認可不備(BOLA)のように、APIで頻発する問題に焦点が当たっています。両方を併用するのが基本です。
JWTを使えば認証は安全ですか?
JWTは仕組みの一つであり、それ自体が安全を保証するものではありません。署名(alg: none不許可)、有効期限や発行者などの標準クレームの検証、ログアウト時の失効など、検証を正しく実装して初めて安全に使えます。
レート制限はどこにかければよいですか?
API Gateway、リバースプロキシ、アプリケーションのいずれか単一ではなく、多層で組み合わせるのが堅牢です。少なくとも認証前のエンドポイント(ログインなど)と、高コストな処理には必ず上限を設けてください。
GraphQLやgRPCでも同じ考え方でよいですか?
認証認可・リソース消費の制御・入力検証という柱は共通です。ただしGraphQLは1クエリで深くネストした取得ができるため、クエリの深さや複雑さの制限という固有の論点が加わります。プロトコルごとの注意点は別途確認してください。

まとめ

APIセキュリティの実装チェック

  • 認証(誰か)と認可(何をしてよいか)を分けて、両方をサーバー側でリクエストごとに検証しているか
  • オブジェクト取得・更新時に、それがリクエスト元の所有物か(BOLA対策)を毎回照合しているか
  • 受け取るフィールドを許可リストで限定し、返すフィールドをDTOで明示しているか
  • レート制限・ペイロード上限・ページング上限を設け、高コスト処理に追加のしきい値を置いているか
  • 入力検証をサーバー側で許可リスト方式で行い、出力時のエスケープと組み合わせているか
  • 稼働中のAPIを棚卸しし、古いバージョンや野良エンドポイントを放置していないか

APIセキュリティは、特別な魔法ではなく「リクエスト1本ごとに、誰が・何に・どれだけ・どんな形でアクセスしているかを、サーバー側で必ず検証する」という一貫した姿勢の積み重ねです。画面を前提にした安心感を捨て、すべてのエンドポイントが直接叩かれる前提で設計することが、最初の一歩になります。Webアプリ全般のリスクの全体像は

もあわせてご覧ください。

出典・参考

この記事をシェア

関連する記事