こんにちは!Webアプリケーションの開発やインフラの管理、毎日お疲れ様です。
システムを作るとき、「この機能は一般のユーザーさんだけ」「こっちは管理者さんだけ」というように、誰がどこまで触れるかを決める仕組みを作りますよね。いわゆる「権限管理」というやつです。
実はこの権限管理、ちょっとした設計ミスやうっかりミスが原因で、一般のユーザーがこっそり管理者になってしまうという恐ろしい事態(特権昇格)を引き起こしてしまうことがあるんです。
今回は、セキュリティの世界の裏側も知る私と一緒に、この権限分離の不備(RBAC/ABACの設計ミス)がなぜ起きるのか、そしてどうやってそれを防げばいいのかを、身近な例えを交えながら一歩ずつ優しく学んでいきましょう!
—
1. 家の鍵に例えて考える「認可(Authorization)」の仕組み
まずは、「認証」と「認可」の違いからおさらいしておきましょう。セキュリティの話でよく出てくるこの2つ、最初は混同しやすいですよね。
- 認証(Authentication):「私は〇〇です」と証明すること。
- *例え:* 家の玄関のドアに「合鍵」を差し込んで、自分がその家の住人だと証明することです。
- 認可(Authorization):証明した人が「何をしていい人なのか」の権限を確認すること。
- *例え:* 家に入ったあと、リビングには自由に入っていいけれど、開かずの金庫がある「秘密の部屋」には、お父さんとお母さん(管理者)しか入れない、というルールを決めることです。
今回焦点を当てるのは、この後者の「認可」の部分です。
もし、泥棒(悪意ある攻撃者)が一般の住人の合鍵(一般ユーザーのアカウント)を手に入れたとします。その泥棒が、鍵の掛かっていない「秘密の部屋」のドアノブをガチャリと回して中に入れてしまったら……大変ですよね。これが、システムにおける「権限分離の不備」なんです。
—
2. RBACとABACってなに? 難しく考えずに仕組みを理解しよう
権限を管理する代表的な仕組みとして、RBAC(ロールベースアクセス制御)とABAC(属性ベースアクセス制御)というものがあります。名前は難しそうですが、中身はシンプルです。
RBAC(Role-Based Access Control)
- 考え方: 「役職(ロール)」ごとにできることを決める仕組みです。
- *例え:* 会社で「一般社員」の人はコピー機と自分のデスクだけ、「部長」の人はそれに加えて人事評価ファイルが見られる、というようにグループ分けするやり方です。
ABAC(Attribute-Based Access Control)
- 考え方: 「状況や属性(条件)」を細かく見て判断する仕組みです。
- *例え:* 部長であっても、「平日の昼間(時間)」に、「会社のパソコン(場所)」からアクセスしているときだけ、人事評価ファイルを開いてもいいよ、という厳しい条件をつけるやり方です。
システムを作る際、「この人は一般ユーザーだからこのページはダメ」と、RBACのグループ分けやABACの条件チェックのどこかに穴(設計ミス)があると、攻撃者にそこを突かれてしまうのです。
—
3. なぜ起きる? 攻撃者が狙う「権限の穴」
実際のWebアプリで、よくある失敗例を見てみましょう。
例えば、一般ユーザーが自分のプロフィール画面を見るためのURLが、次のようなものだったとします。
https://example.com/user/profile?id=105
ここで、もしあなたが意地悪な攻撃者だったらどう考えるでしょうか?
「もしかして、この id=105 の数字を id=1 に書き換えたら、管理者のプロフィールや設定画面が見えちゃうんじゃないか?」と試してみたくなるはずです。
もし、このURLにアクセスされたとき、システムが「今ログインしている人は、本当にこの id=1 の情報を覗いていい人なのかな?」というチェック(認可の確認)を忘れているとどうなるでしょう?
なんと、一般ユーザーの権限のまま、管理者のデータをごっそり盗み見たり、書き換えたりできてしまうのです。これが、現場で最もよくある「IDOR(安全でない直接オブジェクト参照)」と呼ばれる権限の不備です。
—
4. 実装で防ぐ! 最小権限の原則と安全なコード例
では、こうした悲劇を防ぐためにはどうすればよいのでしょうか?
基本の考え方は「最小権限の原則(Least Privilege)」です。これは、「人は必要最低限の鍵だけ持ち、それ以外はすべて開けられないようにする」という防犯の鉄則と同じです。
PHPを使った簡単なサンプルコードで、正しい認可チェックの書き方を見てみましょう。
❌ 危険なコード例(チェックをサボっている状態)
<?php
// ユーザープロフィールを表示する処理(危険!)
$userId = $_GET['id']; // URLから送られてきたIDをそのまま受け取る
// データベースからユーザー情報を取得して画面に表示する
$userInfo = getDatabaseRow($userId);
echo "ようこそ、" . $userInfo['name'] . " さん!";
// 問題点:ログイン中のユーザーが「本当にこのIDの情報を閲覧する権限があるか」を一切確認していません!
?>
⭕ 安全なコード例(きちんと認可チェックを入れている状態)
<?php
session_start();
// 1. まず、今ログインしているユーザーのIDとロールをセッションから確実に取り出す
$currentUserId = $_SESSION['user_id'] ?? null;
$currentUserRole = $_SESSION['user_role'] ?? 'guest';
// 2. URLから閲覧したい対象のIDを取得する
$targetUserId = $_GET['id'] ?? null;
// 認証チェック:ログインしていなければログイン画面へ跳ね返す
if (!$currentUserId) {
header("Location: /login.php");
exit;
}
// 3. 【重要】「今ログインしている人」が「見ようとしている人」と一致するか、あるいは管理者権限を持っているか厳しくチェックする!
if ($currentUserId != $targetUserId && $currentUserRole !== 'admin') {
// 権限がない場合は、エラーを出して処理をピタッと止める
http_response_code(403); // アクセス禁止を示すHTTPステータスコード
die("エラー:このページにアクセスする権限がありません。");
}
// 4. チェックを無事にクリアした場合のみ、安全にデータを取得して表示する
$userInfo = getDatabaseRow($targetUserId);
echo "ようこそ、" . htmlspecialchars($userInfo['name'], ENT_QUOTES, 'UTF-8') . " さん!";
?>
このように、データを表示したり変更したりする手前で、「本当にこの操作をしていい身分(権限)なのかな?」とプログラムのなかで何重にも確認することが、何よりも強力な防御になります。
—
5. まとめ:一歩ずつ、安全なシステムを作っていきましょう
今回は、RBACやABACといった権限分離の仕組みと、それが破られてしまう特権昇格のメカニズムを、身近な例えとコードを交えて解説しました。
- 認証だけでなく、「認可(何ができるかのチェック)」を必ずセットで実装する。
- URLのパラメータなどを信用せず、「今操作している人は誰で、どんな権限を持っているか」をサーバー側で常に確認する。
- 自分に与えられた最低限の権限だけで作業する「最小権限の原則」を意識する。
最初は難しく感じるかもしれませんが、日々の開発のなかで「この処理、本当に権限チェック入ってるかな?」と一瞬立ち止まる習慣をつけるだけで、セキュリティ事故のリスクはグッと減らすことができます。
一歩ずつ、確実に安全なシステムづくりを楽しんでいきましょう!それではまた次回の記事でお会いしましょう!
コメント