認可の穴を塞げ: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は、すべて偽物かもしれない」と疑うことだ。
面倒かもしれない。しかし、この数行の認可ロジックをサボった結果、数万件の個人情報が漏洩すれば、エンジニアとしてのキャリアだけでなく、会社の存続をも左右する。
「動くコード」ではなく「壊れないコード」を書くこと。それが、我々エンジニアに課せられた最大のミッションだ。次回のデプロイから、必ずこのチェックリストを思い出してくれ。
コメント