1. 縦の認可と横の認可 #vertical-vs-horizontal
認可の脆弱性は大きく2分類されます。
- 縦(Vertical) — 役割の違い。一般会員が管理画面にアクセスできてしまう、編集者がシステム管理機能を使えてしまう、等。役職や権限レベルを越えるパターン。
- 横(Horizontal) — 同じ役割の他ユーザのリソースにアクセス。会員 A が会員 B の注文履歴を見れてしまう、ユーザ A が ユーザ B のプロフィールを編集できてしまう、等。
実務では横の認可不備が圧倒的に多い。理由は「権限分離」は意識しても、「同じ権限でも他人のデータは見せない」という制御は実装が複雑で漏れやすいから。
2. IDOR(Insecure Direct Object Reference) #idor
URL や API の ?order_id=12345、/users/789/profile のような直接識別子(ID)を、別の値に変えるだけで他人のデータにアクセスできる脆弱性。
// うつろい EC の注文詳細 https://shop.utsuroi.example.com/orders/12345 // 攻撃者が ID を変えてみる https://shop.utsuroi.example.com/orders/12346 https://shop.utsuroi.example.com/orders/12347 ... // 各リクエストで「現在のログインユーザがその注文の所有者か」を確認していなければ、 // 全注文の詳細(住所・購入商品・金額)が見えてしまう
ありがちな実装ミス:
// 認可チェックなし $order = Order::find($_GET['order_id']); return $order->detail();
// 認可チェックあり
$order = Order::find($_GET['order_id']);
if ($order->user_id !== $current_user->id) {
abort(403);
}
return $order->detail();
3. 予測不可能な ID は対策ではない #opaque-id
「連番 ID は推測されやすいから UUID(xyz-abc-123-...)に変えれば安全」という対策はよく見ますが、これはセキュリティ・バイ・オブスキュリティの典型例で、根本対策ではありません。
- UUID は推測しにくいが、別経路(URL 共有、ログ漏洩、Referer ヘッダ)で漏れる可能性
- サーバ側の認可チェックがなければ、漏れた瞬間に全データにアクセス可能
- 「URL を知っているなら見て良い」という設計は権限管理として不健全
本質的な対策は「リクエストごとに、ログインユーザがそのリソースに対する権限を持つかをサーバ側で確認する」こと。ID の予測可能性は副次的な要素にすぎない。
実際の事例として、外部に渡す ID を UUID にしているシステムでも、内部的に連番 ID を別途持っており、ログや古いエンドポイントから連番 ID が漏れて IDOR に至るパターンがあります。「ID を見えなくする」ではなく「ID を見ても権限がなければ何もできない」が正解。
4. API エンドポイントの認可 #api-control
モダンな Web アプリは画面のリンクだけでなく、API エンドポイント(/api/orders/12345)を提供しています。「画面に表示されないリンク」は攻撃者にも見えていないと油断するのは危険。
攻撃者は次のような手段で API を発見します:
- ブラウザ開発者ツールの Network タブで実際のリクエストを観察
- JavaScript ファイルから API のパスを抽出
- OpenAPI / Swagger スペックの公開
- 類似サービスの API 構造から推測(
/api/v1/users/{id}等)
API レベルでも認可チェックを実装する必要がある。「画面で隠したから安全」ではない。
5. OWASP Top 10 の1位 #owasp-top1
OWASP Top 10 — Web アプリケーションの最重要セキュリティリスクを10個に絞ったランキングは、2021年版で「Broken Access Control」が1位になりました。CVE 件数、影響範囲、検知の難しさ、悪用の容易さ全てで上位スコア。
なぜ Access Control の不備がこれほど多いかの理由:
- 機能追加のたびにエンドポイントが増え、新しい認可チェックを実装し忘れがち
- フレームワークが「自動」で認可してくれる仕組みが少ない(認証はフレームワークがやってくれるが、認可はビジネスロジック依存)
- テストで検出しにくい(正常な機能テストではバグが現れない)
- 「最初は管理者だけが使う」と思っていた機能に一般ユーザがアクセスできる経路が後から生まれる
実務では、認可チェックを横断的なミドルウェアとして実装する設計(全ての API リクエストで自動的に「リソース所有者か?」を判定する仕組み)が推奨されます。手書きで毎回 if 文を書く実装は漏れる。