- OWASP Top 10の首位。認証済みユーザーが他人のデータに触れる『認可漏れ』の恐怖
- URLのIDを書き換えるIDOR攻撃を防ぐ、WHERE句への`userId`強制バインド術
- フロントエンドの表示制御に頼らない、サーバー側での『デフォルト拒否』設計の徹底
不適切なアクセス制御とは?
不適切なアクセス制御は、認証されたユーザーが、本来許可されていないデータや機能にアクセスできてしまう脆弱性です。
「ログインはできているが、他人のマイページが見えてしまう」といった状態が代表例です。OWASP Top 10(2021年版)において第1位に選ばれるほど、現在最も広く、かつ深刻な問題となっています。
脆弱性が発生する仕組み:IDORの例
IDOR(不適切な直接オブジェクト参照)は、URLのIDを書き換えるだけで他人のリソースにアクセスできる典型的なミスです。
// 脆弱なAPIエンドポイント
// GET /api/v1/orders/1001
app.get('/api/v1/orders/:id', async (req, res) => {
const order = await db.orders.find(req.params.id);
// ↑「この注文がログインユーザーのものか」のチェックが漏れている
res.json(order);
});
攻撃者が 1001 を 1002 に書き換えるだけで、他人の注文履歴を盗み取ることができてしまいます。
根本的な対策:サーバー側での確実な認可チェック
最も重要な原則は、**「すべてのリクエストにおいて、サーバー側でオーナーシップ(権限)を再確認すること」**です。
対策のポイント(仕様まとめ)
| 対策手法 | 実装の方向性 | エンジニアとしての所感 |
|---|---|---|
| 認可の再検証 | セッションから取得したユーザーIDをもとにクエリを組み立てる | order.id = ? だけでなく AND user_id = ? を加えるのが基本です。 |
| 最小権限の原則 | ユーザー役割(Role)ごとにアクセス可能な機能を物理的に制限する | 管理者以外は特定のAPIエンドポイント自体を呼び出せないようにミドルウェアでガードします。 |
| Indirect Reference | 1001といった生IDではなく、推測困難なUUIDやハッシュを使用する | 直接的なリソース推測を難しくする補助的な対策です。 |
2026年3月の現場感: UI で隠しても API は正直です
2026年3月の Reddit や bug bounty 系の議論では、不適切なアクセス制御は今も「UI では隠れているのに API を直接叩くと通る」形で大量に見つかっています。特に SaaS やマルチテナント製品では、フロントエンド側のロール制御が整っていても、バックエンドの一覧 API、CSV エクスポート、管理フラグ更新、検索 API のどこかでテナント境界が抜ける例が非常に多いです。
このテーマで2026年らしいのは、単なる IDOR だけでなく、「複数テナントを横断する自動化 API」や「AI エージェントが裏で呼ぶ内部 API」まで含めて認可を統一する発想が重視されている点です。権限を画面ごとに後付けすると破綻しやすいため、今の実務では ポリシーを一箇所へ寄せる、あるいは DB レベルの RLS と組み合わせる構成が評価されています。
私がレビュー時にまず見るのは次の4点です。
- 一覧取得と詳細取得の両方で tenant / owner 条件が入っているか
- 管理者権限の例外処理が散在していないか
- エクスポート、検索、バルク更新 API が本体と同じ認可を通っているか
- UI 非表示だけで済ませた機能がないか
トラブルシューティング:漏れのない認可実装手順
アクセス制御漏れを防ぐために、開発プロセスに以下のチェックを組み込みます。
1. 認可ミドルウェアの導入
個別の関数内でチェックするのではなく、ルーティングレベルで一括してアクセス権を検証する仕組みを使います。
2. クエリレベルでの制限
// OKな実装例
const order = await db.orders.findOne({
where: {
id: req.params.id,
userId: req.user.id // ログインユーザーに紐づくデータのみに制限
}
});
3. デフォルト拒否(Deny by Default)
「許可されたもの以外はすべて拒否」という設計思想に基づき、新しいAPIを追加した際にうっかり公開状態にならないようにします。
4. テナント境界を共通部品に寄せる
マルチテナント環境では、各クエリに手で tenantId を足す方式だと漏れやすいです。ORM のスコープ、ポリシーエンジン、DB の Row Level Security などを使い、開発者が毎回思い出さなくても境界が入る構造へ寄せる方が安全です。
5. 正常系テストだけでなく「他人として試す」
認可の不備は、自分のデータでしか試さないと見逃します。通常ユーザー、別テナント、権限昇格前後、無効化済みアカウントの4パターンで API テストを持つと、かなり再発しにくくなります。
実務で使える確認表
| レイヤー | 確認内容 | 典型的な漏れ |
|---|---|---|
| ルーティング | 認証・認可ミドルウェアが必須化されている | 新規APIだけ保護漏れ |
| クエリ | userId や tenantId を条件へ強制 |
詳細 API だけ条件なし |
| 管理系機能 | 例外権限の根拠が明文化されている | 管理者フラグを見て即許可 |
| バッチ / エクスポート | 画面APIと同じ認可ルールを再利用 | CSV 出力だけ全件取得 |
| 監査ログ | 誰がどの権限で何に触れたか残る | 事故後の調査ができない |
よくある質問(FAQ)
Q: フロントエンドでボタンを非表示にすれば安全ですか?
A: いいえ。攻撃者は開発者ツールなどで直接リクエストを飛ばすため、フロントエンドの表示制御にはセキュリティ上の効果はありません。必ずサーバー側でチェックが必要です。
Q: 管理画面のURLを秘密にすれば大丈夫ですか?
A: いわゆる「隠蔽によるセキュリティ(Security through obscurity)」であり、全く安全ではありません。クローラーやリファラーからURLは容易に漏洩します。
Q: UUID に変えれば IDOR は解決しますか?
A: いいえ。推測しにくくなるだけで、認可漏れ自体は残ります。UUID は補助策として有効ですが、必ずサーバー側の所有権チェックと組み合わせる必要があります。
実機でこう動いた、という記録
この記事は『私の環境ではこう動き、こう直った』という一次記録を中心に組み立てています。汎用的なノウハウ集ではなく、私が実際に踏んだエラーメッセージ・実行したコマンド・確認した数値をベースに書いているため、再現条件が完全一致しないケースもあります。差分があれば、コメントや問い合わせから知らせてもらえると助かります。