論文で最も多かった脆弱性は「アクセス制御の不備」でした。ログインはできる。でも、ログインした人が「見てよいもの」と「見てはいけないもの」の線引きが甘い。この章では、その線引きがサーバー側とデータベース側でどう守られているかを、設定画面の目視とAIによるコードレビューで確かめます。
この章の学習目標
- 認証(誰か)と認可(何が許されるか)の違いを、経理の承認権限にたとえて説明できるようになる
- アクセス制御の典型的な不備3つを知り、自分のアプリがどの状態にあるかを判断できるようになる
- Supabaseの行単位のアクセス制御(RLS)が有効か、ポリシーが「自分の行だけ」になっているかを管理画面で読めるようになる
認証と認可は、別の話
似た言葉ですが、答えている問いが違います。
- 認証
- 「あなたは誰か」を確かめること。IDとパスワード、ワンタイムコード、顔認証。英語では authentication。
- 認可
- 「あなたに何が許されているか」を決めること。自分の口座だけ見られる、管理者だけ全員の一覧を見られる。英語では authorization。
経理の現場なら、社員証でオフィスに入れることが認証、入ったあとに承認できる伝票の範囲や金額の上限が認可です。社員証を持っているからといって、他部署の伝票を承認してよいことにはなりません。ところがアプリでは、「ログインしていればよし」で止まっているものが非常に多いのです。
第1章で見た論文の数字を思い出してください。アクセス制御の不備が339件(28.58%)で1位、認証の不備が176件(14.84%)で3位。合わせると全体の4割を超えます。論文は原因の最大のものを「暗黙のセキュリティ規則の欠落」(658件、55.5%)としています。「他人のデータは見せない」は、誰も指示に書かないけれど当然守られるべき規則です。AIはそれを、言われなければ省きます。「他部署の伝票は閲覧不可」がマニュアルに書いていなくても当然であるのと同じですが、AIにはその「当然」がありません。
典型的な3つの不備
OWASP Top 10:2025のA01「アクセス制御の不備」に挙げられている例のうち、個人開発のアプリで特によく起きるのは次の3つです。自分のアプリがこの状態になっていないか、という目で読んでください。
1. データのIDさえ分かれば、持ち主でなくても読める
取引履歴の画面が /transactions/105 のようにIDを含んでいて、サーバーが「この105番は、今ログインしている人のものか」を確かめずに返している状態です。IDOR(Insecure Direct Object Reference。他人のデータのIDを指定すると見えてしまう不備)と呼ばれます。経理で言えば、伝票番号を知っているだけで他部署の伝票が閲覧できてしまう状態です。
2. 管理者専用の画面が、ログインだけで開ける
/admin のような管理画面が、「ログインしているか」しか見ていない状態です。一般の利用者のメニューからリンクを消しているだけでは、守りになりません。「管理者である」という確認が、サーバー側で行われている必要があります。
3. ブラウザ側だけで権限を判定している
「管理者でなければボタンを表示しない」「残高不足なら送信ボタンを無効にする」が、ブラウザの中だけで行われている状態です。第8章で見た通り、ブラウザ側は利用者が自由に書き換えられます。サーバー側で同じ判定をしていなければ、判定は存在しないのと同じです。
3つに共通するのは、「サーバー側で、所有者または役割を確認しているか」という一点です。確認する場所は、画面ではなく、データを取り出す・書き換える処理の一つひとつです。読み取りだけ守って、更新や削除が抜けていることもよくあります。
Supabaseを使っているなら:RLSを目視する
Supabaseには、データベースの側で「誰がどの行を読めるか」を決める仕組みがあります。RLS(Row Level Security、行単位のアクセス制御)です。テーブルごとに「有効/無効」があり、有効にしたうえで「ポリシー」(誰にどの行を許すかの規則)を書きます。
ここで第9章の鍵の話がつながります。Supabaseの公式ドキュメントによれば、公開用の鍵(anon/publishable)で届く要求は、RLSのポリシーに従って絞られます。RLSが無効なテーブルは、公開用の鍵だけで全行が読める状態になりえます。正確には、そのテーブルがAPIから呼び出せる場所(標準では public スキーマ)にあり、公開用の鍵の役割(anon)に読み取りの権限が与えられている場合です。多くのSupabaseプロジェクトではこれが初期状態です。公開用の鍵はブラウザに置く前提なので、この条件のもとでは、RLSが無効であることは「そのテーブルは誰でも読める」とほぼ同じ意味になります。一方、管理用の鍵(service_role/secret)はRLSを素通りします。だからブラウザに出してはいけないのでした。
確認は管理画面で行います。
- Table Editor で各テーブルを開き、RLSが有効になっているかを見ます。無効のテーブルには、その旨の表示が出ます。
- Authentication の Policies で、テーブルごとのポリシーを読みます。利用者のデータを持つテーブルなら、条件に
auth.uid() = user_idのような「ログイン中の本人のID = その行の持ち主のID」という形が入っているのが基本です。 - ポリシーは操作ごと(select、insert、update、delete)に分かれます。RLSが有効なテーブルでは、ポリシーが書かれていない操作は拒否されます。ですから、更新や削除のポリシーが無いこと自体は抜けではありません。読み取り専用にしたいテーブルなら、それが正しい設計です。見るべきは逆に、許しすぎているポリシーです。条件が
true(誰にでも許可)になっている、本人かどうかの条件が入っていない、追加(insert)のときに「書き込む行の持ち主が本人か」を確かめていない、といった形が典型的な抜けです。 - Supabaseの管理画面には、設定の問題を自動で指摘する Security Advisor があります。「RLSが無効なテーブルがある」といった警告が出ていないかを読みます。
経理の比喩で言えば、RLSは「総勘定元帳の閲覧権限を、勘定科目や部門ごとに設定する機能」です。会計システムで部門別の閲覧制限をかけていれば、画面の作りが多少甘くても、元帳の側で止まります。サーバー側の確認とRLSの二重にしておくのが、内部統制でいう「二重の防御線」です。
セッション、ログアウト、パスワードの保存
認証そのものについても、3点だけ見ておきます。
- セッション(ログイン状態の記憶)。ログアウトしたあと、ブラウザの「戻る」で保護された画面が表示されないか。表示されるなら、ログアウトがブラウザ側の見た目だけで、サーバー側の状態が残っている可能性があります。ただし、「戻る」で画面が出ないことは確認の入り口にすぎず、サーバーが古いログイン状態を受け付けなくなったことの証明にはなりません。Supabaseの公式ドキュメントによれば、ログアウトしても、すでに発行済みのアクセストークン(ログイン状態を示す通行証)は有効期限が来るまで使えます。期限の長さ(標準は1時間)を確認し、必要なら短くします。
- パスワードの保存。パスワードは元に戻せない形(ハッシュ)に変換して保存するのが原則で、入力された文字をそのまま保存する「平文保存」は論外です。Supabase Authのような認証サービスに任せているなら、自分のテーブルにパスワードの列は存在しないはずです。もし自分のテーブルに
passwordという列があるなら、その中身が元の文字のままでないかを確認してください。OWASPのPassword Storage Cheat Sheetが、使うべき方式を示しています。 - パスワードの長さ。OWASP ASVS 5.0の目安は最小8文字、15文字以上を強く推奨です。認証サービスの設定画面で最小長を確認します。
このリポジトリのアクセス制御をレビューしてください。 対象:データを取得・作成・更新・削除するすべてのサーバー側処理(APIルート、サーバーアクション、サーバー関数、データベースへの直接の問い合わせ)。 それぞれについて、次を表にしてください。 1. 処理の名前とファイル名、行番号 2. 操作の種類(取得/作成/更新/削除) 3. ログイン済みかどうかをサーバー側で確認しているか(はい/いいえ/不明) 4. そのデータの所有者が、ログイン中の利用者本人であることをサーバー側で確認しているか(はい/いいえ/該当なし) 5. 管理者専用の処理の場合、管理者であることをサーバー側で確認しているか 6. 判定がブラウザ側だけで行われている箇所があれば、その指摘 Supabaseを使っている場合は、各テーブルのRLSの有効/無効と、ポリシーが select・insert・update・delete のそれぞれについて「本人の行だけ」になっているかも併せて報告してください。 「いいえ」や「不明」になった箇所を、深刻な順に並べてください。 修正はまだしないでください。報告だけお願いします。
演習2:認証と権限の点検
- 第8章の全体図から、信頼境界をまたぐ矢印のうち「IDを含むもの」と「管理者だけが使うもの」を点検ノートに書き出します。
- 上の定型文でAIにレビューさせ、「いいえ」「不明」の箇所を点検ノートに写します。
- Supabaseを使っているなら、管理画面で全テーブルのRLSの有効/無効と、ポリシーの内容(本人の行だけになっているか、4つの操作が揃っているか)を目視し、Security Advisorの警告を読みます。
- 自分のアプリにテスト用のアカウントを2つ作ります(自分が管理するメールアドレスを使います。実在の同僚のアドレスは使いません)。一方のアカウントで作ったデータの詳細画面が、もう一方のアカウントやログインしていない状態からは「権限がありません」「見つかりません」と拒否されることを確かめます。期待する結果は「見えないこと」です。
- 管理者用の画面が、一般のテスト用アカウントでは拒否されることを確かめます。
- ログアウト後に「戻る」で保護された画面が残らないことを確かめます。あわせて、アクセストークンの有効期限を点検ノートに書きます(ログアウト後も期限までは使えるため)。
- 拒否されるべきものが表示された箇所は、点検ノートに「要対応」と書き、第13章の手順で修正したあと、この演習をもう一度やります。
まとめ
- 認証は「誰か」、認可は「何が許されるか」。ログインできることと、他人のデータを見てよいことは別。アクセス制御の不備は論文で最多(339件、28.58%)。
- 典型的な不備は、IDだけで他人のデータが読める、管理画面がログインだけで開く、権限判定がブラウザ側だけ、の3つ。共通の対策は「データを扱う処理の一つひとつで、サーバー側が所有者または役割を確認する」こと。
- Supabaseでは、全テーブルでRLSを有効にし、ポリシーを「本人の行だけ」にする。RLSが無効なテーブルは、公開用の鍵だけで全行が読める。サーバー側の確認とRLSで二重に守る。
出典
- OWASP Top 10:2025(日本語版)A01 アクセス制御の不備(OWASP, 2025)
- OWASP Cheat Sheet Series「Authorization Cheat Sheet」(OWASP)
- OWASP Cheat Sheet Series「Password Storage Cheat Sheet」(OWASP)
- Supabase Docs「Row Level Security」(Supabase)
- Supabase Docs「Understanding API keys」(Supabase)
- Junquan Deng, Zhiyu Fan, Ruijie Meng「Understanding the (In)Security of Vibe-Coded Applications」(arXiv:2606.23130、2026年6月、v4は2026年9月14日)
- OWASP Application Security Verification Standard 5.0(OWASP, 2025)