IDORはなぜ消えないのか?「権限チェック漏れ」という名の爆弾を解体する
現場でコードレビューをしていると、必ずと言っていいほど遭遇するのが「IDOR(Insecure Direct Object Reference:不適切なアクセス制御)」の脆弱性だ。
「自分自身のプロフィールを表示する画面だから、URLの `?user_id=123` をいじくる奴なんていないだろう」
そう思っていないか? もしそうなら、今すぐその甘い考えを捨ててほしい。攻撃者はツールを使い、数秒で連番のIDを総当たり(ブルートフォース)する。君が書いたその一行のクエリが、顧客の個人情報を全世界にさらす引き金になるんだ。
今日は、IDORを根本から叩き潰すための「認可の集中管理」という考え方を叩き込む。
—
1. なぜIDORがすり抜けてしまうのか?
根本的な原因は、「認証(Authentication)」と「認可(Authorization)」を混同していることにある。
ログインしていること(認証済み)と、そのデータにアクセスする権利があること(認可済み)は別物だ。多くの開発者は「ログインさえしていれば、どのデータにアクセスしてもOK」という設計をしがちだ。これが脆弱性の温床になる。
攻撃者の視点(PoC)
攻撃者は、君たちのアプリケーションをこう見ている。
1. `GET /api/v1/orders/5001` にアクセス。
2. 自分の注文情報が見れることを確認。
3. `5001` を `5002` に書き換えてリクエスト。
4. 他人の注文詳細がレスポンスとして返ってくる。
たったこれだけだ。サーバーサイドで「ログインユーザーID」と「リソースの所有者ID」を照合していない場合、防御は無力化する。
—
2. 認可ロジックを「集中管理」せよ
個々のコントローラーに毎回 `if ($user->id !== $order->owner_id)` と書くのはやめよう。コードベースが肥大化すれば、必ず書き漏らしが発生する。
解決策は、「認可レイヤーの抽象化」だ。
実装例:Python (FastAPI/Dependency Injection)
FastAPIの依存注入(DI)機能を使って、リソースへのアクセス権をゲートウェイ的にチェックする方法だ。
from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session
認可チェックを独立した関数として定義(再利用可能)
def get_verified_order(order_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db)):
order = db.query(Order).filter(Order.id == order_id).first()
if not order:
raise HTTPException(status_code=404, detail=”Not Found”)
# ここが肝:所有者IDとログインユーザーIDを突き合わせる
if order.owner_id != current_user.id:
# 攻撃者にヒントを与えないため、権限がない場合も404を返すのがベターな場合もある
raise HTTPException(status_code=403, detail=”Forbidden: 他人のデータには触らせない”)
return order
@app.get(“/orders/{order_id}”)
def read_order(order: Order = Depends(get_verified_order)):
# ここに到達した時点で、認可は既に完了している
return order
このアプローチの利点は、コントローラーがビジネスロジックに集中できることだ。「認可チェックを忘れる」というヒューマンエラーを、仕組みで防ぐことができる。
—
3. テストで「他人のデータ」を強制的に叩く
コードを書くだけで満足してはいけない。CI/CDパイプラインに、「意図的に不正アクセスを試みるテストケース」を組み込む必要がある。
def test_idor_vulnerability(client, user_a, user_b, order_of_user_b):
“””
User A が User B の注文にアクセスした際、403が返ることを保証するテスト
“””
token_a = login(user_a)
# 他人のリソースIDを直接指定
response = client.get(
f”/orders/{order_of_user_b.id}”,
headers={“Authorization”: f”Bearer {token_a}”}
)
assert response.status_code == 403 # 403 Forbidden が返るべき
このテストが通るまで、その機能は「未完成」と見なすくらいの厳しさが、君のプロダクトを守る盾になる。
—
4. 最後に:現場のエンジニアへ
セキュリティは「機能」ではなく「品質」だ。
- 推測不可能なIDを使う: `order_id=5001` と連番にするのではなく、`UUID v4` を採用するだけで、総当たり攻撃の難易度は天文学的に跳ね上がる。
- 認可の集中化: 上記のように、認可ロジックを一箇所に寄せ、共通関数として強制的に適用させる設計を徹底してほしい。
- WAFは最後の砦: WAFで「パスパラメータの異常な推移」を検知することもできるが、根本解決はあくまでソースコード側にある。
IDORは、少しの注意深さと、設計段階での「性悪説」に基づいたロジック構築があれば、完全に撲滅できる脆弱性だ。今日から君の書くコードに、この「認可のゲート」を組み込んでみてくれ。それが、君がプロのエンジニアとして信頼を勝ち取るための第一歩だ。

コメント