OWASP A01:2021 — Broken Access Control の全貌

OWASP Top 10 はウェブアプリケーションセキュリティの国際標準として広く参照される脆弱性ランキングです。 2021年版で最も注目されたのが、Broken Access Control(壊れたアクセス制御) の 2017年の5位から第1位への大幅な昇格です。2025年版でもこの順位は維持されています。

統計は深刻です。評価対象となったアプリケーションの94%で何らかの形の Broken Access Control が発見され、 関連する CVE(共通脆弱性識別子)の総数は19,013件、インシデント記録数は318,487件に上ります。 第2位以下と比べても圧倒的な発生頻度であり、セキュリティ対策の最優先事項として位置付けられています。

含まれる主要な攻撃パターン

OWASP A01 に含まれる攻撃パターンは多岐にわたります。URL 操作による他ユーザーのリソース閲覧、 API エンドポイントへの直接アクセスによる管理機能の不正実行、JWT クレームの改ざんによるロール変更、 CORS の設定ミスによるクロスオリジンアクセスなどが代表例です。 これらに共通するのは「サーバー側で適切な認可チェックが行われていない」という根本原因です。

BOLA / IDOR — オブジェクトレベル権限チェック漏れ

定義と発見の経緯

BOLA(Broken Object Level Authorization)、別名 IDOR(Insecure Direct Object Reference) は、 認可されていないユーザーが他のユーザーのオブジェクト(レコード、ファイル、データ等)に直接アクセスできる脆弱性です。 OWASP API Security Top 10 では A01:2023 として第1位に位置付けられており、 API セキュリティにおいて最も危険かつ発生頻度の高い問題とされています。

典型的なシナリオは単純です。/api/invoices/12345 にアクセスすると自分の請求書が表示される場合、/api/invoices/12346 に変更するだけで他人の請求書が閲覧できてしまうケースです。 サーバーは「12346 という ID の請求書を返す」という処理は正しく実行しますが、 「12346 はリクエストしたユーザーの請求書か」というチェックが欠落しています。

脆弱なコードと修正例

// NG: 所有者チェックなし — IDが推測可能なら誰でも閲覧できる
app.get('/api/invoices/:id', authenticate, async (req, res) => {
  const invoice = await Invoice.findById(req.params.id);
  if (!invoice) return res.status(404).json({ error: 'Not found' });
  res.json(invoice);
});

// OK: ユーザーIDとセットで検索 — 他ユーザーのIDを指定しても取得不可
app.get('/api/invoices/:id', authenticate, async (req, res) => {
  const invoice = await Invoice.findOne({
    _id: req.params.id,
    userId: req.user.id,  // ← 認証済みユーザーのIDを必ず組み合わせる
  });
  if (!invoice) return res.status(404).json({ error: 'Not found' });
  res.json(invoice);
});

修正版のポイントは「クエリ条件に必ず所有者情報を含める」ことです。 仮に攻撃者が他ユーザーの _id を知っていても、userId の不一致によりnull が返り、404 レスポンスとして処理されます。

BOLA 対策の3原則

  1. 全クエリへの所有者チェック組み込み: オブジェクトを取得するすべてのクエリで リクエストユーザーの ID を検索条件に必ず含める。ORMのスコープ機能を活用すると漏れを防げる。
  2. 予測困難な UUID の使用: 連番 ID(1, 2, 3...)は推測が容易なため、 UUID v4 等のランダム値を使用する。ただしこれは根本対策ではなく、所有者チェックが主対策となる。
  3. レート制限でブルートフォースを防止: UUID でもブルートフォース攻撃で有効な ID を探索される可能性があるため、 エンドポイントにレート制限を設けて大量アクセスを遮断する。
graph TB
  Attacker([攻撃者])
  BOLA[BOLA/IDOR 攻撃]
  HorizEsc[水平権限昇格]
  AdminAcc[管理者アカウント乗っ取り]
  VertEsc[垂直権限昇格]
  FullCtrl[システム完全制御]
  Attacker -->|IDを変えてAPIを叩く| BOLA
  BOLA -->|他ユーザーデータ閲覧| HorizEsc
  HorizEsc -->|管理者のIDを発見| AdminAcc
  AdminAcc -->|管理機能へアクセス| VertEsc
  VertEsc -->|全データ・全機能にアクセス| FullCtrl
  style Attacker fill:#ef4444,stroke:#dc2626,color:#fff
  style BOLA fill:#f97316,stroke:#ea580c,color:#fff
  style HorizEsc fill:#eab308,stroke:#ca8a04,color:#000
  style AdminAcc fill:#f97316,stroke:#ea580c,color:#fff
  style VertEsc fill:#ef4444,stroke:#dc2626,color:#fff
  style FullCtrl fill:#dc2626,stroke:#b91c1c,color:#fff
BOLA/IDORから権限昇格連鎖へ: 水平昇格で管理者アカウントを特定し、垂直昇格でシステム完全制御へ至る危険なチェーン

垂直権限昇格 vs 水平権限昇格

垂直権限昇格 — 一般ユーザーが管理者機能へ

垂直権限昇格(Vertical Privilege Escalation)は、一般ユーザーが 管理者のみ操作できるはずの機能にアクセスできてしまう脆弱性です。 権限レベルが「垂直方向」に上昇することからこの名称がついています。

代表的な攻撃手法として以下があります。

  • パラメータ改ざん: ?role=admin?admin=true といった クエリパラメータを追加することで管理者権限を偽装する。サーバー側が URL パラメータを信頼している場合に有効。
  • ヘッダーベースの迂回: X-Original-URL: /adminX-Rewrite-URL: /admin ヘッダーを送ることで、リバースプロキシのルーティングを欺く。
  • 隠し管理画面への直接アクセス: robots.txt に記載された クローラー除外パスを参照し、非公開の管理 URL へ直接アクセスする。 UI に管理ボタンが表示されなくてもエンドポイントが生きていれば突破できる。

水平権限昇格と Confused Deputy Problem

水平権限昇格(Horizontal Privilege Escalation)は、 同じ権限レベルを持つ他のユーザーのリソースにアクセスできてしまう脆弱性です。 前述の BOLA/IDOR はこの水平権限昇格の典型例です。

水平昇格が特に危険なのは、垂直昇格への連鎖を引き起こすことがある点です。 一般ユーザーAが水平昇格で管理者ユーザーBのオブジェクトにアクセスできた場合、 管理者Bのセッショントークンや認証情報を取得→管理者として機能を実行、というチェーンが成立します。

関連する設計上の問題として Confused Deputy Problem があります。 これは「権限を持つ代理人(Deputy)が、誰のために行動しているかを確認せず、 第三者に不正に利用される」問題です。API ゲートウェイやプロキシサービスが リクエスト元を適切に検証せずに上位権限で処理を実行してしまうケースがこれに該当します。 OAuth 2.0 のsubクレームを常にチェックし、代理実行の文脈でも 元のユーザー ID を一貫して伝播させることが対策となります。

よくある実装ミスとアンチパターン

アンチパターン何が問題か正しい対策
フロントエンドのみのアクセス制御UIでボタンを非表示にするだけ。APIを直接叩けば制御を完全に迂回できるサーバーサイドで必ず認可チェックを実装。フロントエンドの制御はUX改善目的のみ
Permission Bloat(権限の肥大化)「とりあえず広めに」でロールや権限を付与。攻撃対象面が拡大し侵害時の被害が増大最小権限の原則を徹底。定期的に未使用権限を棚卸しして削除する
静的リソースへの認可チェック漏れS3バケット・CDN・ファイルサーバーなど、APIとは別経路のリソースが無認可でアクセス可能すべてのリソースアクセス経路に認可チェックを実装。署名付きURLやPre-signed URLを活用
エラーメッセージによる情報漏洩403 Forbiddenと404 Not Foundの使い分けが不適切。存在するかどうかが攻撃者に漏れる認可されていないリソースへのアクセスは403ではなく404を返すことでリソース存在を秘匿する
キャッシュと権限変更のタイミング問題権限を剥奪してもキャッシュが残り、しばらくは旧権限でアクセスできてしまうキャッシュTTLを短く設定。権限変更時はキャッシュを即座に無効化する仕組みを整備する

権限マトリクステスト

テスト設計の基本 — ロール × エンドポイント × 期待ステータス

アクセス制御のテストは、「どのロールが」「どのエンドポイントを」「どの操作で呼んだとき」 「どのステータスコードが返るべきか」をマトリクスとして定義することから始まります。 このマトリクスが存在することで、実装漏れとリグレッションの両方を体系的に検出できます。

エンドポイント匿名一般 UserEditorAdmin
GET /posts200200200200
POST /posts401403200200
DELETE /posts/:id401403200*
/ 403
200
GET /admin401403403200

* Editor は自分の記事のみ DELETE 200(BOLA チェック)。他の Editor の記事は 403。401=未認証、403=認可なし

pytest による自動化例

# test_permission_matrix.py
import pytest
import httpx

BASE_URL = "http://localhost:8000"

# テストマトリクスを定義: (ロール, メソッド, パス, 期待ステータス)
MATRIX = [
    ("anonymous", "GET",    "/api/posts",       200),
    ("anonymous", "POST",   "/api/posts",       401),
    ("user",      "POST",   "/api/posts",       403),
    ("user",      "DELETE", "/api/posts/other", 403),   # 他ユーザーの記事
    ("editor",    "POST",   "/api/posts",       200),
    ("editor",    "DELETE", "/api/posts/own",   200),   # 自分の記事はOK
    ("editor",    "DELETE", "/api/posts/other", 403),   # 他ユーザーの記事はNG
    ("admin",     "GET",    "/api/admin",       200),
    ("user",      "GET",    "/api/admin",       403),
]

TOKENS = {
    "anonymous": None,
    "user":      "Bearer user_token_here",
    "editor":    "Bearer editor_token_here",
    "admin":     "Bearer admin_token_here",
}

@pytest.mark.parametrize("role,method,path,expected", MATRIX)
def test_permission(role, method, path, expected):
    headers = {}
    if TOKENS[role]:
        headers["Authorization"] = TOKENS[role]

    resp = httpx.request(method, f"{BASE_URL}{path}", headers=headers)
    assert resp.status_code == expected, (
        f"[{role}] {method} {path} => {resp.status_code} (expected {expected})"
    )

このテストは CI(GitHub Actions 等)の pull request チェックに組み込み、必須ゲートとして設定することを推奨します。 権限マトリクスの変更がテストファイルにも反映されない限り、マージを許可しないよう設定することで アクセス制御のリグレッションを防止できます。

形式的検証ツール — 数学でポリシーの正しさを証明する

Alloy Analyzer — RBAC モデルの自動検証

権限マトリクステストが「実装の正しさ」を検証するのに対し、形式的検証(Formal Verification)は「設計の論理的正しさ」を数学的に証明します。 MIT が開発した Alloy Analyzer は、Relational Logic と SAT ソルバーを組み合わせた モデル検査ツールで、RBAC の設計に特に適しています。

-- Alloy によるRBAC + SoD(職務分離)の形式検証
sig User {}
sig Role {}
sig Permission {}

-- ユーザー・ロール・権限の関係定義
sig RBAC {
  user_roles : User -> Role,
  role_perms : Role -> Permission,
}

-- SoD制約: Approver と Executor を同一ユーザーが持てない
pred SoDConstraint[r: RBAC] {
  all u: User |
    not (Approver in u.(r.user_roles) and Executor in u.(r.user_roles))
}

-- 検証: SoD制約のもとで权限分離が保たれるかを確認
assert NoApproverExecutor {
  all r: RBAC | SoDConstraint[r] implies
    no u: User | Approver in u.(r.user_roles) and Executor in u.(r.user_roles)
}

check NoApproverExecutor for 5 -- 5要素までの全ケースをSATで網羅探索

Alloy の強みは 「反例の自動生成」です。 制約を満たさない状態が存在する場合、SAT ソルバーがその具体的な反例(ユーザー・ロール・権限の組み合わせ)を提示します。 ロール爆発(Role Explosion)が発生しているかどうかも、 ロールの数の増加パターンを形式的にモデル化することで検出できます。

Z3 SMT ソルバー — XACML ポリシーの競合検出

Microsoft Research が開発した Z3 SMT ソルバーは、 第5章で解説した XACML ポリシーエンジンの形式的検証に活用できます。 XACML ポリシーを論理式に変換し、競合(Conflict)や冗長性(Redundancy)を自動検出します。

# Python で Z3 を使った XACML ポリシー競合検出
from z3 import *

# ポリシー条件を論理変数として定義
user_role = String('user_role')
resource_type = String('resource_type')
action = String('action')
time_of_day = Int('time_of_day')

# Permit ルール: Editorが記事を更新できる
phi_permit = And(
    user_role == "editor",
    resource_type == "article",
    action == "update"
)

# Deny ルール: 業務時間外(18時以降)は全操作を拒否
phi_deny = And(
    time_of_day >= 18,
    action == "update"
)

# 競合検出: phi_permit AND phi_deny が同時に成立するか?
solver = Solver()
solver.add(And(phi_permit, phi_deny))

if solver.check() == sat:
    print("競合あり: Permit と Deny が同時に成立するモデルが存在します")
    print(f"反例: {solver.model()}")
else:
    print("競合なし: ポリシーに矛盾はありません")

上記では「業務時間外は全更新を拒否」というルールと「Editor は記事を更新できる」という Permit ルールが競合する可能性を検出しています。 Z3 はこの論理式が SAT(充足可能)であれば具体的な反例モデルを出力するため、 ポリシー設計者がどのケースで競合が発生するかを即座に把握できます。

Tamarin Prover と NIST SP 800-192

Tamarin Prover(ETH Zurich 開発)は、セキュリティプロトコルの形式検証に特化したツールです。 OIDC プロトコル全体の認可フローを自動検証した実績があり、 OAuth 2.0 の認可コードフローにおける CSRF 耐性などの安全性要件を記号的に証明できます。

NIST SP 800-192(アクセス制御ポリシーの検証ガイドライン)は、形式的検証の対象として 以下の3つを定義しています:

  1. 矛盾性(Inconsistency): 同一条件で Permit と Deny が同時に成立しないか
  2. 不完全性(Incompleteness): 全ての正当なアクセス要求に対してポリシーが応答を返すか(未定義状態がないか)
  3. 安全性要件(Safety Properties): SoD 制約・最小権限原則等の設計要件がモデル上で保たれているか

形式的検証はすべてのプロジェクトに必須ではありませんが、 金融・医療・政府系システムのように高い保証レベルが求められる場合や、 ABAC ポリシーが複雑化して人間のレビューだけでは見落としが生じる場合には、 CI パイプラインへの組み込みが強力なセーフティネットとなります。

脆弱性対策チェックリスト

本章で解説した内容をもとに、アクセス制御の脆弱性対策チェックリストをまとめます。

カテゴリチェック項目対策ツール / 手法
BOLA / IDOR全クエリにオブジェクト所有者チェックを組み込んでいるかコードレビュー・ORM スコープ
垂直権限昇格サーバー側で管理者機能の認可チェックを実装しているか(UI 依存ではない)ミドルウェア認可・RBAC
静的リソースS3 / CDN / ファイルサーバーに認可チェックが適用されているか署名付き URL・Bucket Policy
マトリクステスト全ロール × 全エンドポイントの期待ステータスがテストされているかpytest / CI 必須ゲート
形式的検証SoD 制約・ポリシー競合がモデルレベルで検証されているかAlloy Analyzer / Z3
エラー設計403 / 404 の返し分けでリソース存在を秘匿しているかエラーハンドリング統一
レート制限ID ブルートフォース攻撃対策のレート制限が設定されているかAPI Gateway レート制限

理解度チェック

問題 0 / 50%
Q1

OWASP Top 10 2021年版でBroken Access Controlが第1位に昇格した背景として、最も適切な説明はどれですか?

キーボード: 1〜4 で選択、Enter で回答