権限昇格は、本来許されない操作や他人の権限を、一般ユーザーが手に入れてしまう脆弱性です。 対策の勘所は、画面での出し分けに頼らず、サーバ側で毎回権限を確認できているかどうかです。
権限昇格とは
権限昇格(privilege escalation):ユーザーが本来与えられていない権限を取得し、許可されていない操作を実行できてしまう脆弱性です。 サーバ側での権限チェックの欠落が根本原因になります。
権限昇格は2つに分類されます。
- 水平方向の権限昇格:同じ権限レベルの他ユーザーになりすまし、他人のデータや機能にアクセスする
- 垂直方向の権限昇格:一般ユーザーが管理者など上位の権限を得て、本来使えない管理機能を実行する
いずれも、データの改ざんや流出、システム全体の掌握といった被害につながります。 とくに垂直方向の昇格で管理者権限を得られると、全ユーザーのデータや設定に手が届くため、被害が一気に広がります。
権限昇格が起きるパターン
典型的な弱点は次のとおりです。
- 管理機能へのリンクやボタンを画面上で隠すだけで、APIには権限チェックがない(隠蔽による防御)
- リクエストの
roleやis_adminといったパラメータを信用し、クライアント側の値で権限を判定している - 一部の管理エンドポイントだけ認可を掛け、新規に追加したエンドポイントで掛け忘れる
- 権限を明示的に許可する方式ではなく、危険な操作だけを個別に禁止する方式で、抜けが生じる
- ユーザー自身が自分の権限や所属を変更できる更新経路が空いている
- IDやパラメータの書き換えで、他ユーザーや上位ロールのリソースを操作できる(水平方向の典型)
共通するのは「権限の判定を画面やクライアントの入力に任せ、サーバ側で毎回確認していない」という点です。
対策法
原則は「すべての操作について、サーバ側でログイン中のユーザーの権限を確認する」ことです。
- 画面での出し分けとは独立に、サーバ側で操作ごとに権限を判定する。これが本命の対策になる
- 既定を拒否(デフォルトdeny)にし、明示的に許可された操作だけを通す。禁止リスト方式にしない
- 権限や所属はサーバが保持する値で判定し、リクエストのパラメータを信用しない
- 管理機能は共通の認可処理を通す設計にし、エンドポイントごとの掛け忘れを防ぐ
- 権限の変更操作を記録し、想定外の昇格が起きていないか監査できるようにする(補助策)
画面で隠す実装は防御にならず、本命はサーバ側の権限チェックとデフォルト拒否です。 権限を要する操作を洗い出し、共通の認可層に集約するのが費用対効果の高い進め方になります。
Rails での事例
危険なのは、画面側でリンクを出し分けているだけで、コントローラに権限チェックがない形です。
# 危険:管理者メニューを画面で隠しているだけ。URL を直接叩けば誰でも実行できる
class Admin::UsersController < ApplicationController
def destroy
User.find(params[:id]).destroy!
redirect_to admin_users_path
end
end
エンドポイントごとに if current_user.admin? を書く方式は、新しいアクションを追加したときの掛け忘れがそのまま穴になります。
管理系のコントローラは共通の基底クラスを明示的に継承し、チェックを1箇所に集約します。
class Admin::BaseController < ApplicationController
before_action :require_admin!
private
def require_admin!
head :forbidden unless current_user&.admin?
end
end
class Admin::UsersController < Admin::BaseController
def destroy
User.find(params[:id]).destroy!
redirect_to admin_users_path
end
end
Admin::は定数の名前空間にすぎず、配下のコントローラがAdmin::BaseControllerを自動で継承するわけではありません。
各コントローラでAdmin::BaseControllerを明示的に継承し、その継承関係をテストで固定します。
RSpec.describe Admin::UsersController, type: :controller do
it "管理者用の基底クラスを継承している" do
expect(described_class).to be < Admin::BaseController
end
end
リソースごとの細かい認可(水平方向の昇格対策)が必要なら、Pundit のような認可ライブラリを併用します。
Punditのverify_authorizedをafter_actionとして設定すると、authorizeを一度も呼んでいないアクションで例外が発生し、認可の掛け忘れをテストや実行時に検出できます。
基底クラスの明示継承を確認するテストとverify_authorizedは役割が異なるため、両方を使います。
あわせて、権限そのものを外部入力で更新させないことも確認します。
params.require(:user).permit(:role) のように許可リストへ role を含めてしまうと、マスアサインメント経由の垂直昇格が成立します。
まとめ
権限昇格対策の要点を整理します。
- 根本原因は「権限の判定を画面やクライアントの入力に任せていること」。メニューを隠すのは防御ではなく、URLを直接叩かれれば実行できてしまう
- 本命の対策は、画面での出し分けとは独立にサーバ側で操作ごとに権限を判定すること。既定を拒否(デフォルトdeny)にし、禁止リスト方式にしない
- 権限や所属はサーバが保持する値で判定する。リクエストの
roleやis_adminを信用しない Admin::名前空間だけでは共通の基底クラスが継承されないため、管理系コントローラでは明示的に継承し、その関係をテストしたうえで、Punditのverify_authorizedのような認可チェック漏れの検出も併用する- 水平方向(他人のリソース)と垂直方向(上位ロール)は原因が別。前者はリソース単位の認可、後者はロール判定とマスアサインメント対策で塞ぐ
権限を要する操作を洗い出し、共通の認可層へ集約する作業が対策の中心です。個別のアクションにif current_user.admin?を書き足していく方式は、新しいアクションを追加した瞬間に穴が開きます。
関連して読める記事
水平方向の権限昇格は、IDやパラメータの書き換えで他人のリソースを操作されるIDORとほぼ同じ現象です。垂直方向の昇格でよくある入り口は、更新パラメータにroleを紛れ込ませるマスアサインメントで、許可リストに何を含めるかがそのまま権限設計になります。トークン側で権限を持たせている場合は、署名検証を誤るとroleの改ざんがそのまま昇格に直結します。

IDOR(安全でない直接オブジェクト参照)とは?起きるパターンと対策法
IDOR(安全でない直接オブジェクト参照)の仕組みと起きるパターン、所有者チェックを軸にした対策を整理します。RailsのOrder.find(params[:id])がそのまま穴になる理由と、current_user起点の取得への直し方を具体的なコード例で示します。OWASP API Security Top 10で1位のBOLAに相当する脆弱性です。
2026年7月23日
マスアサインメントとは?起きるパターンと対策法
マスアサインメントの仕組みと起きるパターン、Strong Parameters による許可リスト化を軸にした対策を整理します。permit! で全属性を許可する形と、JSON.parse が返す素の Hash が Strong Parameters の保護対象外になる見落としがちな経路、role のような内部属性を許可リストから外す設計を具体的なコードで示します。
2026年7月25日
JWT認証の脆弱性とは?署名検証の不備で起きるパターンと対策法
JWT(JSON Web Token)の署名検証の不備で認証が丸ごと回避される仕組みと起きるパターン、署名アルゴリズムの固定と失効設計を軸にした対策を整理します。Ruby の jwt gem で署名を検証しない decode の危険例と、algorithm を明示した安全な検証、localStorage 保管をやめて HttpOnly Cookie かメモリ保持へ寄せる判断基準を具体的なコードで示します。
2026年7月25日