Dance with Agents

「誰でも見えるURL」を直す——認証・認可の不備とIDOR

実践

Webアプリの主要セキュリティリスク一覧で触れた「認証・認可の不備」を、具体例とあわせて掘り下げます。

認証と認可は、別のもの

まず言葉の整理から。この2つは似ているのに、指しているものは別です。

言葉 問うていること
認証(authentication) あなたは誰ですか?(ログインできるか)
認可(authorization) あなたは、これをしてよい人ですか?(権限があるか)

ログイン機能を作ると、多くの場合「認証」はできています。パスワードが合っていればログインできる、という部分です。問題は、そのあとです。 ログインした人が「これを見てよいか」「これを変更してよいか」までを、毎回確認できているとは限りません。

典型的な事故:IDOR

代表的な穴がIDOR(Insecure Direct Object Reference、安全でない直接オブジェクト参照)です。名前は難しく見えますが、起きていることは単純です。

自分の注文履歴を見るページが、こういうURLだったとします。

https://example.com/orders/3

3という番号は、自分の3番目の注文だとします。ここで、番号を4に変えてアクセスしたら、他人の注文が見えてしまった——これがIDORです。

なぜ起きるかというと、サーバー側の処理が「ログインしているか」だけを確認して、「この注文は、リクエストしてきた本人のものか」までは確認していないからです。ログインチェック(認証)は通っているので、エラーにならずにそのままデータが返ってきます。

ブラウザの表示を隠すだけでは対策にならない

「ボタンやリンクを、権限のない人には表示しない」という対策だけでは不十分です。画面に表示されていなくても、そのデータを返すAPI自体には、URLさえ分かれば誰でも直接アクセスできるからです。ブラウザの開発者ツールでAPIの呼び出し先を見れば、隠した画面の裏側にあるURLは簡単に分かります。

見た目を隠すのは利便性のための工夫であって、セキュリティの対策はサーバー側で行う必要があります。

対策の具体策

  • すべてのAPI・サーバー側の処理で、データを返す前に「これは誰のデータで、リクエストしてきた人はそれを見る権限があるか」を確認する。 「ログインしているか」のチェックとは別に、「このデータの持ち主は本人か」のチェックを、データを返す直前に必ず入れる
  • 連番のIDに頼りすぎない。 1, 2, 3...のような連番は、他人の番号を推測しやすくします。UUID(推測しにくいランダムな識別子)を使うと当てずっぽうでの到達は難しくなりますが、これは補助的な対策です。 IDが推測しにくくても、権限チェック自体を省略していい理由にはなりません
  • データベース側でも権限を強制する仕組みがあるなら活用する。 SupabaseやFirebaseのようなサービスには、RLS(Row Level Security、行単位のセキュリティ)と呼ばれる、「このデータは、このユーザーしか読み書きできない」という規則をデータベース自身に持たせる仕組みがあります。アプリ側のコードでチェックを書き忘れても、データベース側で防げるという二重の備えになります
  • 管理者用のページ・APIは、権限チェックをひときわ厳重にする。 一般ユーザー向けの機能より、影響範囲が大きいためです

エージェントへの頼み方

「ユーザー個別のデータを返しているAPI・ページを一覧にして、それぞれで『リクエストしてきた人がそのデータの持ち主かどうか』のチェックが入っているか確認して」のように頼むと、抜けている箇所を洗い出しやすくなります。IDORはコードを読んだだけでは気づきにくいので、URLの番号を変えて実際にアクセスしてみる、という手動での確認も有効です。

まとめ

  • 認証(誰か)と認可(してよいか)は別物。 ログイン機能があっても、認可のチェックが抜けていることがある
  • 典型的な事故がIDOR——URLの番号を変えるだけで、他人のデータにアクセスできてしまう穴
  • 画面から隠すだけでは対策にならない。 APIは直接叩かれることを前提に、サーバー側で権限を確認する
  • 対策の基本は、データを返す直前に「持ち主は本人か」を毎回確認すること。RLSのような仕組みがあれば、データベース側の備えも重ねる

「ログインできたから安全」ではなく、「ログインした人が、そのデータを見てよい人か」まで確認して、はじめて安全と言えます。

セキュリティの記事

セキュリティのページへ →