こんにちは!Webアプリケーションを作ったり、会社のITまわりを担当したりする中で、「セキュリティってなんだか難しそうだな…」と感じていませんか?
今回は、Webシステムのセキュリティ診断(ペネトレーションテスト)の現場でも本当によく見つかる、そして初心者の方でも一番直感的に理解しやすい「IDOR(Insecure Direct Object Reference:安全ではない直接オブジェクト参照)」という脆弱性についてお話していきます。
小難しい専門用語はなるべく使わず、私たちの身近にある「家の鍵」や「郵便受け」に例えて優しく解説していきますので、一歩ずつ一緒に学んでいきましょう!
—
1. 「IDORってなぁに?」身近な例えで理解するアクセス制御
まずは、IDORがどういう仕組みで起きるのかをイメージするために、こんなシチュエーションを想像してみてください。
あなたはとあるマンションに住んでいます。あなたの部屋番号は「101号室」です。
郵便受けの鍵がしっかりかかっていれば、他の人から手紙を盗まれる心配はありませんよね。でも、もしその郵便受けが…
- 「ダイヤル錠がついているけれど、数字を
101から102に変えるだけで、隣の人の郵便受けがパカッと開いてしまう」 - 「そもそも鍵がついておらず、誰でも自由に他人の郵便受けのフタを開けられる」
そんな状態だったらどうでしょう?これって、すごく危ないですよね。
Webの世界でもこれと全く同じことが起きています。これがBroken Access Control(不適切なアクセス制御)であり、その代表格がIDORです。
ログインしているから大丈夫、ではない?
よくある誤解が、「うちのシステムはパスワードを入れてログインしないと使えないから安全だよね」というものです。
先ほどのマンションの例で言えば、「マンションのオートロック(ログイン)を突破して中に入った住人が、他の人の部屋のドアノブをガチャガチャ回したら、鍵がかかっておらず勝手に入れてしまう状態」がまさにIDORです。
ログインしているユーザーであっても、「自分以外の人のデータを見ていい権限があるかどうか」をシステムがきちんとチェックしていないと、この問題が発生してしまいます。
—
2. 攻撃者はどうやってIDORを見つけるのか?(メカニズムの解説)
ペネトレーションテスト(攻撃者の視点で行う安全性のテスト)の現場では、私たちはどのようにこの弱点を見つけているのでしょうか。その手口を少しだけ覗いてみましょう。
例えば、あなたが自分のプロフィール画面を開いたとき、ブラウザのアドレスバー(URL)が次のような形になっているとします。
https://example.com/user/profile?id=1001
この id=1001 という部分は、データベース上で「あなたを識別するための番号(オブジェクト参照)」です。ここで、セキュリティのテストをする私たちは、次のような意地悪な(でも攻撃者は必ず試す)ことを考えます。
- 「自分のIDが
1001なら、この数字を1000に書き換えたらどうなるだろう?」 - 「もしかして、他のユーザーの個人情報やクレジットカード情報が見えてしまうのでは?」
もしここで、URLの数字を 1000 に書き換えただけで、全く知らない他人の名前やメールアドレスが表示されてしまったら…これがIDORの成立です。システムが「今このリクエストを送っている人が、本当にそのデータを見てもいい人柄かどうか」を確認していないために起きてしまいます。
—
3. 脆弱なコードと安全なコードの比較
それでは、開発の現場でどうしてこれが起きてしまうのか、具体的なプログラム(PHPを例にします)を見てみましょう。
【危険な例】IDORがあるプログラム
以下のコードは、URLから受け取った id をそのままデータベースに問い合わせて、データを画面に出してしまっている例です。
<?php
// 危険なコード例:ログインしているかの確認はあるが、権限のチェックがない!
session_start();
$my_user_id = $_SESSION['user_id']; // 現在ログインしている自分のID
// URLパラメータからユーザーIDを取得(例: ?id=1002)
$target_id = $_GET['id'];
// データベースから対象のデータを取得
// ★問題点:他人のIDを指定されても、そのままデータを取ってきちゃいます!
$sql = "SELECT * FROM users WHERE id = " . $target_id;
$result = db_query($sql);
$user_data = db_fetch($result);
// 画面にデータを表示
echo "名前: " . $user_data['name'];
echo "メール: " . $user_data['email'];
?>
このコードでは、たとえあなたがログインしていても、URLの ?id= の部分を書き換えるだけで、データベースにある他のユーザーの情報を自由に見放題になってしまいます。
—
【安全な例】しっかりアクセス制御(認可)を入れたプログラム
では、これをどう直せばよいのでしょうか?答えは簡単です。「今リクエストしている人と、取得しようとしているデータが本当に一致しているか(あるいは管理者権限があるか)」をプログラムでしっかり比較するのです。
<?php
// 安全なコード例:認可ロジックをしっかりと実装する
session_start();
$my_user_id = $_SESSION['user_id']; // 現在ログインしている自分のID
$target_id = $_GET['id'];
// ★対策:URLで指定されたIDと、今ログインしている人のIDが一致するかチェック!
if ($target_id != $my_user_id) {
// 一致しない場合はエラーにする(または403 Forbiddenを返す)
header("HTTP/1.1 403 Forbidden");
echo "アクセス権限がありません。";
exit;
}
// 一致した場合のみ、安全にデータを取得して表示する
$sql = "SELECT * FROM users WHERE id = ?";
$stmt = db_prepare($sql);
$stmt->execute([$target_id]);
$user_data = $stmt->fetch();
echo "名前: " . $user_data['name'];
echo "メール: " . $user_data['email'];
?>
このように、「自分のデータ以外は触らせない」という防衛線をプログラムの中に必ず引くことが、Broken Access Controlを防ぐ一番の近道になります。
—
4. 防御側としての意識とHTTPヘッダーの役割
ペネトレーションテストやインフラ構築の現場では、プログラムの修正だけでなく、通信の仕組みやヘッダーの役割を正しく理解することも大切です。
よくある勘違いとして、「隠しページにしておけば誰もURLに気づかない(Security through obscurity)」という考え方があります。例えば、URLに推測しにくい長くてランダムな文字列(UUIDなど)を使ったとしても、それ自体は完全な解決策(認可の代わり)にはなりません。
なぜなら、ブラウザの履歴や、JavaScriptのコード、外部へのリンク(リファラー)などを通じて、その「隠されたURL」が意図せず外部に漏れてしまうことが多々あるからです。URLが推測しにくいことと、アクセス権限があることは、全く別の問題として捉えなければなりません。
また、Webアプリケーションを保護する際には、ブラウザに対して「予期せぬ動作や不正な読み込みを防いでね」と伝える各種セキュリティヘッダー(例:Content-Security-Policy や X-Content-Type-Options など)の設定も重要ですが、IDORのような「ロジックの隙」は、残念ながらサーバーの設定やWAF(Webアプリケーションファイアウォール)だけで完全に防ぐのは難しいのが現実です。だからこそ、アプリを作る段階での「正しいアクセス制御」が命綱になるのです。
—
まとめ:一歩ずつ安全なシステムを作っていきましょう!
今回は、不適切なアクセス制御とIDORの仕組みについて、身近な例えを交えながら解説しました。
- IDORとは?:URLの数字などを書き換えるだけで、他人のデータが見えてしまう恐ろしい脆弱性。
- なぜ起きる?:「ログインしているか(認証)」だけでなく、「そのデータを触る権利があるか(認可)」をプログラムが確認していないから。
- どう防ぐ?:サーバー側で、リクエストされたデータが本当にそのユーザーのものであるかを必ずチェックするコードを書くこと。
セキュリティの対策と聞くと身構えてしまうかもしれませんが、基本の考え方は「うちのシステムは、誰が何を触っていいんだっけ?」というルールを一つひとつ丁寧にプログラムで整理していく作業です。
焦らず、一歩ずつ、安全で信頼されるシステム作りを一緒に楽しんでいきましょう!
コメント