
はじめに
こんにちは。GMO Flatt Security株式会社セキュリティエンジニアの村上です。入社してから5年ほどWebアプリケーションを中心に脆弱性診断を担当しています。
近年、言語やフレームワークの進化により、SQL Injectionなどの古典的脆弱性は減少傾向にあります。しかし、今なお変わらず検出され続けているのが「認可制御不備」です。対策をしようにも、特定の技術要素により発生するものではないため、どこで発生するか一見予測不能に見えます。しかし、我々のように常日頃脆弱性診断を行なっているセキュリティエンジニアの目線では、認可の設計が怪しい、この機能が悪用できそうといったことに気づくことがあります。
AIコーディングの普及により開発スピードは劇的に向上しましたが、生成される膨大なコードすべてに対して、人間がセキュリティを精査し続けることは困難です。一方で認可制御不備は、システム全体の認可ポリシーを理解している必要があるため人間による確認が重要となります。
そこで本記事では、セキュリティエンジニアはどのような点に着目して権限不備の問題を見つけているのかという点を解説し、開発者自身のレビューの精度を高めることを目指します。
まず認可制御不備の概要と発生原因を整理した上で、「セキュリティエンジニアが見たら怪しむ挙動や実装」をそれぞれ具体的に解説していきます。
認可制御不備の原因と現代における脅威
認可制御不備とは、本来許可されていない操作をユーザーが実行できてしまう脆弱性です。その多くは「データの所有権確認」や「親子関係の検証」といったアプリケーション側の実装ロジックの欠落に起因します。この脆弱性は、OWASP Top 10やGMO Flatt Security Top 10において第1位に選出されているように遍在する問題であり、個人情報漏洩などの重大なインシデントを引き起こす最大の要因であり続けています。

この脆弱性は、アプリケーション固有のビジネスロジックに依存するため、従来の自動スキャンツールでは検知が極めて困難でした。しかし、昨今のLLMの発展や弊社が提供する自動脆弱性診断AIエージェントTakumiのようなAIエージェントの登場により、仕様や文脈を汲み取った診断が可能になりつつあります。それでもなお、複雑化するシステム設計の中で認可の網羅性を担保することは難しく、実際の現場では依然として最も頻繁に発見される脆弱性です。
注意するべき実装パターン
パターン1: IDに対してテナント間を跨いで連番が設定されている
診断時の着眼点
- IDとして1,2,1001, user001のような規則性のある値が使用されていないか
- IDが連番になっていないか

診断での考え方
まずは最も単純な事例を見てみましょう。
テナントを跨いで数字が連番になる場合、組織やテナントを跨いで共通の処理やAuto Incrementされるカラムの値が使用されている可能性が高いと推測できます。そのため、実行時にリクエストを送信したユーザーが指定したIDに対して適切な権限を持っているかの検証が行われていない場合、リクエスト先のIDを書き換えることで、異なるテナントや本来権限のないIDに対してアクセスが行えるかを確かめます。
例えばBtoBのサービスで、テナント1でユーザーを作成した際にuser0056というIDが割り振られ、テナント2ではuser0057が割り振られたとします。この結果からユーザーに割り振られるIDはアプリケーション全体で連番として扱われていて、user0000からuser0057までのIDが存在すると推測できます。
さらに、単純な連番でなくても診断では何十個もユーザーを作成して、一見連番でないように見えるが規則性を持つものであれば見比べることで規則性の推測を行います。なお、当社では診断の際に基本的にソースコードを頂き、ソースコードベースでIDの生成ルールが安全なものであるかを確認しているため、推測に不必要な時間は割かずに設計の堅牢さを診断します。
現代ではIDが推測されて攻撃が行えるような明らかに脆弱な実装というのは以前よりも減ってきてはいますが、それでもたまに見かけます。
パターン2: 推測が難しい値で認可を代替している設計
診断時の着眼点
- UUIDや推測が不可能な値がパスやIDとして使用されている
- Presigned URLになっていないS3のURLがレスポンスに含まれる

診断での考え方
一方で、連番が使われておらず推測が難しい値になっていたら安全かというとそれは間違っています。
現代ではIDとして連番のような推測が行いやすい値ではなく、a0eebc99-1c0b-aef8-bb3d-6bb9bd380a11のようなUUIDが使用されることも増えています。このUUIDというのは取り得る値の範囲がとても広く、総当たり攻撃をすることは現実的ではありません。
それでも、リクエストとしてこのような値が送信されていた場合、診断では注意深く見ていきます。
その理由は、UUIDはあくまで推測が難しい値であるだけで、認可制御そのものではないためです。実際の診断でも、UUIDをパラメータとして使用しているリクエストに対して認可制御が行われていないことによる脆弱性というのはよく発見されています。
また、ファイルをAmazon S3などのオブジェクトストレージやAmazon CloudFrontなどのCDNから配信している場合にも注意が必要です。レスポンスとしてS3のパスをユーザーに返すような設計の場合、 S3のパスさえ知っていたら誰でも見れる公開設定になっていることがあります。こちらについても、パスの推測が必要なので攻撃は難しいように見えますが、パスが漏洩した場合や推測された場合に認可制御不備を突かれる可能性があります。
弊社の診断報告書ではUUIDなどの推測が必要な脆弱性の「攻撃成立の可能性」は低く評価されています。しかしながら、ユーザー一覧を取得するAPIやドキュメントの作成者情報など、意図せず値が漏洩する可能性があるため、適切な権限確認の実装が必要です。
パターン3: バリデーションAPIや認可用API
診断時の着眼点
- 実行APIとは別に事前検証を行う専用のAPIが設けられている

診断での考え方
APIリクエストを確認していると、/api/purchase/validateのような事前検証を行う専用のAPIが送信されているログが見つかることがあります。このパターンは更新処理を実際に実行する前に、この操作は実行可能かを確認することを目的としており、特に取り消しが困難な処理で見かけることがあります。
この設計の問題点は、バリデーションを行う処理と変更を行う処理が異なるエンドポイントに分かれて実装されており、それらの処理の整合性が保証されないことです。そのため、画面上からの操作ではバリデーションAPIで弾かれる入力も、変更を行う本命のAPIに直接リクエストを送ることで バリデーションを回避できる可能性があります。
攻撃者はバリデーションAPIを呼び出さずに変更APIを呼び出すプログラムを実装可能であるため、前にバリデーションが呼ばれているはずだから安全という考え方は危険です。バリデーション専用のAPIはあくまで更新処理が実行可能かを事前に確認するためのものであり、変更を行うAPI自身がバリデーションを行うことが必要です。
パターン4: ユーザーやロールの作成・更新・招待機能
診断時の着眼点
- 一般ユーザーから権限の作成・更新を行う機能が利用できる
- 権限変更を伴うAPIを管理者と一般ユーザーで共有している
- ユーザー招待機能を利用する際に権限が選択できる
診断での考え方
ユーザーの追加や権限の変更を伴う機能は、設計のミスによっては一般ユーザーから管理者権限に昇格が可能なため特に注意が必要となる機能です。そのため、不要なパラメータが存在しないか、一般ユーザー権限と管理者権限で同じAPIだが権限によって設定できる権限範囲が変わる挙動が無いかといった観点で見ていきます。
APIのパスが/admin/user/updateのように明確に管理者権限からしかアクセスできないように設定されている場合は、ミドルウェア側で管理者権限以外によるAPI自体への認可制御が行われていることが多く、一般ユーザーによってAPIが悪用されるリスクは低くなります。
しかし、一般ユーザーと管理者が/user/updateという同じAPIを利用しており、権限によって設定可能な範囲が変わるような場合は注意が必要です。パラメータの検証に不備があると、一般ユーザーが自身よりも高い権限を設定できてしまう可能性があります。
新たにユーザーを招待するAPIの場合も同様に、権限を設定するパラメータを変更することで、新たに作成するユーザーに管理者権限を付与したり、異なるテナントのアカウントを作成したりといった多様な脆弱性が発生する可能性があります。こちらもリクエストを送ったユーザーが、その権限よりも高い権限を設定していないかパラメータの値を検証する対策が必要です。
パターン5: 権限の境界が曖昧な設計
診断時の着眼点
- 一般ユーザーから一部管理者機能が実行できる
- ロールなどを用いて特定の機能ごとに権限を割り振ることができる
診断での考え方
「管理者」や「ユーザー」といった権限に加えて、「人事管理者」や「テナント管理者」といった一部管理者権限を使用できる権限がある場合、設計のミスによっては自身の権限よりも高い権限を設定できてしまうことがあります。
他にも、BtoBのSaaSアプリなどでカスタムロールとして特定の機能にのみアクセスできる権限が作成できる場合、設計によっては一般ユーザー権限からユーザー追加機能を悪用して権限昇格が起きる可能性があります。
また、権限設計が複雑になると、OrganizeUserやOrganizeAdminなど似た名前の権限名も増えることで実装時のヒューマンエラーによる誤った権限の付与も発生しやすくなります。
パターン6: 親子関係があるリソースの設計
診断時の着眼点
- 親子関係のあるリソースの全ての子リソースに対して認可制御が行われているか
診断での考え方
フォルダとファイル、組織とユーザー、プロジェクトとタスクなどの親子関係があるリソースがある場合、それぞれに対して個別で認可制御が行われているかを確認します。
実装当初はファイル単位の非公開状態がなくフォルダ単位での認可制御で問題ない場合も、後のアップデートで非公開状態が追加されることによって認可制御不備が発生する場合があります。
例えば、そのような歴史的経緯の影響で親であるフォルダに対しては認可制御が行われているが、ファイルに対しては認可制御が行われていないような場合を考えます。フォルダへのアクセス権限があるユーザーであっても、アクセス権限のないフォルダ内の非公開ファイルには本来アクセスできるべきではありません。しかし、ファイル単位での認可制御がなければ、ファイルIDを直接指定することで非公開ファイルにアクセスできてしまう可能性があります。
パターン7: 複数のIDパラメータがリクエストに含まれている実装
診断時の着眼点
- リソースの識別を行う値が複数含まれていないか
診断での考え方
パラメータを確認していると、1つのリクエストにfile_idとfile_nameのように類似したパラメータが付与されていることがあります。file_idもfile_nameも同じファイルを指定するために使用されている場合、処理の中でどちらの値を使用するかといったパラメータごとの解釈のずれにより認可制御を回避できないかを考えます。
例えば、ファイルにアクセスできるかの確認はfile_id、実際に処理を行う対象はfile_nameのように認可制御で確認する値と実際の処理で使用する値が異なっている場合を考えます。この場合、file_idには攻撃者のアカウントが権限を持つfile_idの値を入れ、file_nameには攻撃者が権限を持たない被害者の値を渡すと、認可制御が回避できてしまう可能性があります。
同様に、JWTやセッションから取得した信頼できる値と、ユーザーからの入力で受け取った値の紐付けを確認せずに一緒に処理に使用してしまったり、複数のIDパラメータの内1つだけ検証して他を暗黙的に信頼して検証を省いてしまうことで認可制御の回避が発生する可能性があります。
パターン8: 他のパラメータから導出可能な値がリクエストに含まれている実装
診断時の着眼点
- 他のパラメータを使用すれば、データベースから取得可能な値がパラメータに含まれていないか
診断での考え方
リクエスト中に、他のパラメータの値から取得できる値や、セッションに紐づいている値がパラメータとして含まれていないかを確認します。類似の役割を持つ複数の値が使われている場合、認可処理ではセッションに紐づいた値に対して検証し、実際の処理はパラメータとして渡された値に対して行うといったことが起こりえます。このような処理の中で、解釈する値のずれが生まれている可能性があります。
例えば、file_idパラメータで指定されたファイルIDを元に、データベースのfolder_idカラムから取得した値に対して認可制御を行ない、実際に更新処理を行う際にはfolder_idパラメータで指定された値を使用するとします。この場合、認可制御を行う値と、実際に更新処理に使用する値が異なるため解釈不備により本来権限のないfolderに対しての変更が行われる可能性があります。
パターン9: SQLのWHERE句の条件がシンプルすぎる実装
診断時の着眼点
- SQLを使用するためのライブラリの利用法は正しいか
- SQLのWHERE句でユーザーを識別する検証済みの値による制限が行われているか
脆弱な例:
# ユーザー入力のfile_idのみで検索
SELECT * FROM files WHERE file_id = {ユーザー入力のfile_id}
安全な例:
# セッションから取得したorg_idで取得範囲を制限
SELECT * FROM files WHERE file_id = {ユーザー入力のfile_id} AND org_id = {セッションから取得したorg_id}
診断での考え方
コード中のSQLにおいて、権限のないユーザーにアクセスできないように制御されているか、使用されている値は信頼できる値なのかといった点を確認します。
現代では昔と違い、フレームワークやライブラリを用いて安全にSQLを使用する方法が一般化しています。それによってSQL Injectionなどの脆弱性は稀なものとなりました。しかし、検証されていない値をそのまま検索に使用した場合や、アクセスできる範囲の制限が行われていない場合、認可制御不備が発生する可能性があります。
例えば、SQLのWHERE句に指定されている条件が少ない場合は要注意です。WHERE句にユーザーから入力された検索したいユーザーIDの値しか指定されていない場合、そのユーザーIDは信頼できる値なのか、異なる組織のユーザーIDを指定した際にどこでエラーとなるのかといった点を深掘りしていきます。
理想的な実装としては、JWTやセッションに紐づいている信頼できる値(ユーザーIDや組織IDなど)と、探したいデータのIDのように、信頼できる値がWHERE句でAND条件で指定されていることです。権限外のユーザーIDなどの値が入力された場合でも、信頼できる値によって取得可能なデータが制限されるようになっていることが求められます。
また、ライブラリの機能によってはORMや認可ライブラリを用いて、アクセスできる範囲をセッションユーザーに紐づいているスコープのみに限定することが可能です。例えばRailsのActive RecordやLaravelのEloquentではスコープ機能を使って取得範囲を制限することができます。実装する際には使用しているライブラリの使用方法が適切であることの確認を行ってください。
パターン10: 権限確認が各エンドポイントごとに呼び出される実装
診断時の着眼点
- 共通処理として実装されたAPIの権限確認関数の実装漏れ、誤った実装
脆弱な例:
# ✅ 権限確認が正しく実装されたエンドポイント
def get_files(request):
check_permission(request.user, "read") # 権限確認あり
return db.get_files()
# ❌ 権限確認の呼び出しが漏れたエンドポイント
def update_settings(request):
# check_permission() が呼ばれていない
return db.update_settings(request.data)
診断での考え方
各エンドポイントごとに、権限確認関数を呼び出す実装がなされている場合は、関数の正しい利用方法の確認を行い、関数の呼び出し漏れや、引数の設定忘れといった実装のミスが発生しているAPIがないかを確認します。
また、バリデーションの処理やデータベースの利用などのセキュリティに関わる関数についても同様に確認することで、実装のミスに気づくことが可能です。
多くのWebフレームワークでは、ルーティングの設定時に権限チェックのミドルウェアを指定できます。この方法だとルーティングを確認することで、リクエストの実装内で権限確認を行うよりも設計と実装における必要となる権限に差がないかを確認することが容易になります。しかしながら、OrganizeUserとOrganizeAdminのように似た名前の権限が複数ある場合、間違った権限を設定してしまってもレビューで見逃してしまうというヒューマンエラーが発生することがあります。そのため、目視でのレビューに加えて、Semgrepのような静的解析ツールによるカスタムルールの作成やテストの実施を推奨します。
終わりに
本稿では、セキュリティ診断の現場で認可制御不備を発見する際に着目している設計・実装のパターンを紹介しました。認可制御不備はツールによる自動検出が難しく、人間による設計レビューやコードレビューが不可欠な脆弱性です。
本記事で紹介した観点が、開発者の皆さまが自身のプロダクトの認可制御を見直す際の手がかりとなれば幸いです。
ここまでお読みいただきありがとうございました。