はじめに
IDOR(Insecure Direct Object Reference)は、ユーザが指定する識別子(ID、ファイル名、トークンなど)を通じて、本来アクセス権のない別ユーザのリソースに到達できてしまう脆弱性です。
原因は単純で、サーバ側で 「そのリクエストが本当にそのユーザに許可されているか」 を検証していないことにあります。この記事では、発見方法(特に Encoded / Hashed ID の扱い方)、テスト手順、実務的な対策、報告テンプレまでを実践的にまとめます。
なぜ危険か
- 個人情報や機密ファイルの漏洩につながる。
- 認可バイパスを足がかりに、さらなる権限昇格や改ざんに発展する。
- API や Web インターフェースの設計ミスがあれば、簡単なリクエスト改ざんで侵害可能。
発生する典型的な場面
- URL のクエリパラメータ:
GET /invoice?id=1001 - POST ボディ/JSON:
{"fileId":"abc123"} - クッキー、Referer、Hidden フィールド、JWT とは別の補助IDなど
- これらが 所有者検証なし に処理されると発生する
Encoded IDs(エンコードされたID)の扱い方
特性
- 一般的に Base64 が多い(
A–Z a–z 0–9 + /と=パディング)。 - Base64 は 暗号化ではなくエンコード。誰でもデコードできる。
テスト手順(実践)
- 対象パラメータが Base64 風か確認(文字セット・
=の有無をチェック)。 - デコード(例:
base64decode.org、ローカルのbase64コマンド)して中身を確認。 - ID 部分を別ユーザの値に書き換え(あるいは簡単な連番に変更)。
- 再エンコードしてリクエストを再送。レスポンスが変われば脆弱性の可能性あり。
注意点
- Base64 の中に JSON や複合データが入っている場合がある(構造を壊さないよう注意)。
- サニタイズや署名(HMAC)が付いていると単純に編集できない場合もある。
Hashed IDs(ハッシュ化ID)の扱い方
特性
- ハッシュ(MD5/SHA1 等)は一方向。理論上は可逆ではないが、実務では辞書/レインボーテーブルや総当たりで逆引きされることがある。
- 小さな連番 ID をハッシュしているだけだと総当たりで簡単に特定される。
テスト手順(実践)
- 発見したハッシュ文字列の形式(長さ)からハッシュ方式を推定
- CrackStation 等のオンラインサービスで逆引きを試す
- 連番 ID の可能性があるなら、自分で
hash(candidate)を生成して照合(スクリプトや hashcat を利用)
注意点
- ソルトや HMAC が使われている場合は成功しにくいが、所有者チェックをサボっているケースがまだある
実務的な探索フロー(チェックリスト)
- 入力を洗い出す:URL、POST、Cookie、Hidden、JSON、API ヘッダ等
- 識別子の種類を判定:整数連番/UUID/Base64/ハッシュ
- 改変可能性を試す:連番増減、Base64 decode→edit→encode、ハッシュ逆引き
- レスポンス確認:HTTP ステータス、コンテンツ差異、ダウンロード可否、漏洩情報の有無
- ログを残す:成功時はリクエスト・レスポンスのスクショ/ペイロード保存(再現性のある証跡)
- 安全に報告:影響範囲、手順、再現用コマンドを開発チームに提出
使用すると便利なツール
- Burp Suite(Proxy、Intruder、Repeater)
- OWASP ZAP
- CLI:
curl,httpie - 文字列処理:
base64(Linux/macOS)、openssl、Python スクリプト - ハッシュ解析:CrackStation、hashcat、John the Ripper
サーバ側での防御(実装上の必須事項)
-
必ずサーバ側で認可(Authorization)を行う
-
resource.owner_id == current_user.idを必ずチェック - 「ログインしているか」だけでは不十分
-
-
予測困難な識別子を使う(補助)
- 単なる対策であり、認可チェックの代替ではない。UUID や長いランダムトークンを使う
-
可能なら ID を送らせない設計
- 例:
GET /me/filesのように現在ユーザのコンテキストで返す
- 例:
-
ハッシュを使う場合でも HMAC やソルトを併用する
- ただし所有者確認は必須
-
監査ログと異常検知
- 短時間に大量の別 ID へアクセスがあればアラートを上げる
- 最小権限・ロールベース/属性ベース認可を導入する
開発者向けコード例(簡潔に)
Python / Flask(擬似コード)
@app.route('/invoice')
def invoice():
invoice_id = request.args.get('id')
inv = Invoice.query.filter_by(id=invoice_id).first()
if not inv:
abort(404)
if inv.owner_id != current_user.id:
abort(403)
return send_file(inv.path)
レポート(脆弱性報告)テンプレ
-
タイトル: IDOR —
/file?id=<編集値>による未承認のファイルアクセス - 環境: production / staging(該当を明記)
-
再現手順:
- ログインユーザ A で
GET /file?id=eyJpZCI6IjEwMDEifQ==を取得(Base64 表示) - デコードして
{"id":"1002"}に編集、再エンコードしてリクエスト - ユーザ B のファイルがダウンロードできる
- ログインユーザ A で
-
期待される挙動: サーバは
file.owner_id == current_user.idを検証し、権限がなければ403 Forbiddenを返すべき - 影響度: 機密ファイル漏洩の可能性
- 推奨対応: サーバ側での所有者確認、監査ログ、短期的にIDの再設計(UUID化)を実施
まとめ
- IDOR の本質は 「認可チェックの欠如」 にある。エンコードやハッシュの有無は実務上の妨げにはならない(工夫次第で解析可能)
- ペネトレーション/セキュリティテストでは 入力を洗い出して改ざんしてみる こと、開発側は 受け取った識別子の所有者確認を必ず行う ことが最も重要
- 実装・設計の両面で対策(API 設計、認可ロジック、監査)を組み合わせることでリスクを低減できる