【実務・中級編】Broken Access Control: IDOR(Insecure Direct Object Reference)の特定と防御 – アプリケーションセキュリティ & 安全な開発防御ガイド

認可の穴を塞げ:IDOR(ID参照の脆弱性)を「設計の常識」で殺す技術

現場でインシデント対応をしていると、SQLインジェクションやXSSよりも遥かにタチが悪いと感じるのが「IDOR(Insecure Direct Object Reference:安全でない直接オブジェクト参照)」だ。

なぜタチが悪いか? それは、「システムが正しく動いているように見えるから」だ。認証は通っている。SQLも正しい。だが、アクセスしてはいけない他人のデータが見えてしまう。攻撃者はツールも使わず、ただブラウザのURLやリクエストのIDを書き換えるだけで、あなたの顧客データベースを丸裸にする。

今日は、この「権限の盲点」をどう埋めるか、現場の泥臭い知見を交えて話そう。

—

1. なぜ「推測可能なID」が命取りになるのか

IDORの本質は「認可(Authorization)の欠落」だ。

例えば、https://example.com/api/orders/12345 というAPIがあるとしよう。多くのジュニアエンジニアは、ここで「ログインしているか(認証)」だけを確認し、満足してしまう。しかし、攻撃者はこう考える。
「12345が見えるなら、12344や12346も見えるんじゃないか?」

これがIDORのPoC(概念実証)だ。特別なツールは不要。curl コマンド一つで、全ユーザーのオーダー情報を抜き出すスクリプトが数分で完成する。

攻撃者が実行する単純なスクリプト例
for i in {10000..20000}; do
curl -H “Authorization: Bearer <攻撃者のトークン>” https://example.com/api/orders/$i
done

これで、認証済みのユーザーが、他人の注文詳細を全件ダウンロードできてしまう。

—

2. 対策の極意:IDを「隠す」のではなく「紐付ける」

よくある誤解が「IDをUUIDにして推測不可能にすれば安全」というものだ。確かに推測は難しくなるが、それは単なる「隠蔽(Security by Obscurity)」であり、本質的な防御ではない。

正解は、リソースを取得するたびに「そのユーザーがそのリソースにアクセス権を持っているか」を必ずDBクエリレベルで検証することだ。

Python (Flask) でのNG例と正解例

【NG:IDだけで取得する】

@app.route(‘/orders/‘)
def get_order(order_id):
# 危険!誰でもIDを知っていれば見えてしまう
order = db.query(“SELECT FROM orders WHERE id = ?”, order_id)
return jsonify(order)

【OK:セッションとIDを組み合わせる】

@app.route(‘/orders/‘)
@login_required # 認証チェック
def get_order(order_id):
# 認可チェック:セッション内のユーザーIDと紐付いているかを確認
current_user_id = session.get(‘user_id’)

# WHERE句に必ず所有者情報を入れる
order = db.query(
“SELECT FROM orders WHERE id = ? AND user_id = ?”,
(order_id, current_user_id)
).fetchone()

if not order:
# 権限がない、または存在しない場合は404を返す(403だと存在確認されるため)
abort(404)

return jsonify(order)

—

3. 実践:インフラ層での多層防御(WAFの活用)

コード修正が間に合わない場合や、レガシーシステムでロジック変更が困難な場合は、WAFで異常なアクセスパターンを遮断する。特に「急激なIDの連続アクセス」はIDORの典型的なシグナルだ。

AWS WAF (Rate-based rule) の設定のヒント:

  • ルール: 特定のURIパターン(例: /api/orders/)に対して、同一IPからのリクエストが一定時間内に閾値を超えた場合、自動ブロックする。
  • 効果: 攻撃者が全IDを総当たりしようとした際、IP制限で強制停止させることができる。

—

4. プロの現場でのチェックリスト

最後に、今日から君がコードレビューで必ず確認すべきポイントをまとめておく。

1. 「IDによる直接参照」を疑え: URLパス、クエリパラメータ、隠しフォームフィールドにIDが含まれていないか?
2. 認可のスコープを限定せよ: find(id) と書くなら、必ず where(user_id=me).find(id) に書き換えろ。
3. エラーメッセージを慎重に: 認可エラーで「アクセス権がありません」と出すと、IDが存在することを認めることになる。基本は404 Not Foundで返せ。
4. 間接参照の活用: どうしても公開すべきIDがあるなら、DBの主キー(1, 2, 3…)をそのまま使わず、クライアント用にはハッシュ化されたトークン(ハッシュID)を別途発行してマッピングさせる設計を検討せよ。

まとめ:セキュリティは「性悪説」で実装する

IDORを防ぐ唯一の近道は、「ブラウザから送られてくるIDは、すべて偽物かもしれない」と疑うことだ。

面倒かもしれない。しかし、この数行の認可ロジックをサボった結果、数万件の個人情報が漏洩すれば、エンジニアとしてのキャリアだけでなく、会社の存続をも左右する。

「動くコード」ではなく「壊れないコード」を書くこと。それが、我々エンジニアに課せられた最大のミッションだ。次回のデプロイから、必ずこのチェックリストを思い出してくれ。

コメント

タイトルとURLをコピーしました