ゆめさくセキュリティ

レッスン 2-4

認可の設計

認証は入口だけ、認可はすべての操作に要ります。

URL の番号を変えるだけで他人の注文が見える穴 (IDOR) を扱います。ログインの確かめ (認証) と、その人がその物を触ってよいかの確かめ (認可) を分け、すべての操作で持ち主を突き合わせる作りと、2 人の利用者で互いに届かないことを固める試験までを扱います。

  • 目安60 分
  • 出典を確かめた日2026年10月24日
  • 修了の条件すべての操作に認可が要る理由を、例を挙げて説明できる

なぜ通るのか

通販の画面で、自分の注文を開くと住所の末尾に番号が付いています。/orders/1 です。この番号を 1 つずらして開いたら、知らない人の名前と住所が出てきた。IDOR (直接の参照の確かめ漏れ) と呼ばれる穴は、こうして見つかります。

ログインはできています。サーバーは「この人は alice だ」と知っています。それでも他人の注文が出るのは、サーバーが「この注文は alice の物か」を確かめていないからです。

認証と認可は別の問い

認証は「あなたは誰か」を確かめます。認可は「その人が、その物に、その操作をしてよいか」を確かめます。OWASP の Authorization Cheat Sheet も、認可を認証とは別の手続きとして扱っています。

ログインの画面を固くしても、認可は 1 つも増えません。認証は入口で 1 回、認可は注文を見る時、取り消す時、住所を変える時と、操作のたびに要ります。

番号は利用者の手の中にある

URL の番号、隠しフィールド、JSON の中の ID は、どれも利用者が書き換えられる値です。画面に他人の注文へのリンクを出していなくても、番号は送れます。OWASP の Cheat Sheet は、口座番号のような内部の ID が住所の問い合わせ部分などに出ること自体はよくあると書き、だからこそ推測されても触れないように作るよう勧めています。

連番は推測しやすいので、番号を長いランダムな値にする手もあります。ただしこれは重ねる守りで、確かめの代わりにはなりません。番号が漏れた時点で同じ穴が開きます。

見る時だけ確かめる、が穴になる

よくあるのは、注文の詳細を見る所では持ち主を確かめているのに、取り消しや住所の変更の所では番号だけで動いている形です。画面からは見る所しか押せないので、作った人は気づきにくい。けれども要求は画面を通らずに直接送れます。

脆いコードと直したコード

ラボの中の小さな関数で比べます。Python に付いている sqlite3 だけで動き、外とは通信しません。注文 1 は alice の紅茶、注文 2 は bob の緑茶です。

番号だけで取り出すか、持ち主と突き合わせるか

脆いコード番号だけで注文を取り出す
def get_order(db, user, order_id):sql = ("SELECT id, owner, item"" FROM orders WHERE id = ?")return db.execute(sql, (order_id,)).fetchone()
直したコードログインした人と持ち主を突き合わせる
def get_order(db, user, order_id):sql = ("SELECT id, owner, item"" FROM orders WHERE id = ?"" AND owner = ?")return db.execute(sql, (order_id, user)).fetchone()

脆い方は user を受け取っているのに使っていません。alice が 2 を頼むと、bob の緑茶が返ります。直した方は、持ち主が alice でない注文は 1 件も返しません。「無い」と「あなたの物ではない」を同じ返事 (見つからない) にすると、他人の番号が実在するかどうかも漏れません。

操作ごとに書くか、1 か所の関所を通すか

脆いコード取り消しは番号だけで動く
def cancel(db, user, order_id):db.execute("UPDATE orders SET st = 'cancel'"" WHERE id = ?", (order_id,))
直したコードどの操作も同じ関所を通す
class NotFound(Exception):pass def owned(db, user, order_id):row = db.execute("SELECT owner FROM orders"" WHERE id = ?",(order_id,)).fetchone()if row is None or row[0] != user:raise NotFound(order_id) def cancel(db, user, order_id):owned(db, user, order_id)db.execute("UPDATE orders SET st = 'cancel'"" WHERE id = ?", (order_id,))

直した方は、持ち主の確かめを owned の 1 か所にまとめ、変える操作はかならずそこを通します。操作を足すたびに確かめを書き写す作りだと、どこか 1 つで書き忘れます。OWASP の Cheat Sheet は、どの規則にも当たらない時は拒む (既定で拒否) ことと、要求のたびに権限を確かめることを勧めています。

ログでの見え方と検知

脆いコードのまま、alice が番号を 1 つずつずらして開くと、記録には成功が並びます。行の 2 つ目は頼んだ人、最後は取り出した注文の持ち主です。この持ち主の列を足しておくと、頼んだ人と持ち主が食い違っていることが一目で分かります。

記録脆いコードのときの記録
127.0.0.1 alice GET /orders/1 200 alice127.0.0.1 alice GET /orders/2 200 bob127.0.0.1 alice GET /orders/3 200 carol

直したあとは、2 と 3 が 404 になります。守る側は、次の物を記録と見張りに入れます。

見る物なぜ効くか
頼んだ人と持ち主の食い違い成功した漏れを、そのまま拾える
1 人が短い間に開く別々の番号の数番号を順に試す動きは、ふだんの利用と形が違う
403 と 404 の急な増え方CISA などは、これを番号の総当たりと結び付けている

ACSC、CISA、NSA の共同の注意喚起は、改ざんの試みを記録して警告を出すこと、ふだんの基準を作って外れた動きを見つけることを勧めています。404 と 403 が番号の総当たりの手口と結び付くことにも触れています。

直し方と、直ったことの確かめ方

自分の PC の中だけで行います。外のサービスや、他人の機械には向けません。

  1. 作業用のフォルダに sqlite3 のデータベースを作り、alice の注文 1 と bob の注文 2 を入れる
  2. 脆い get_order に alice と 2 を渡し、bob の注文が返ることを確かめる
  3. 脆い cancel に alice と 2 を渡し、bob の注文が取り消されることを確かめる
  4. 両方を直したコードに置き換え、同じ入力で何も返らず、取り消しも止まることを確かめる
  5. 下の試験を足し、直す前のコードに戻すと試験が落ちることも確かめる
  6. 終わったら、作業用のフォルダごと消す
Python2 人で互いに届かないことを固める試験
def test_alice_cannot_read_bob(db):assert get_order(db, "alice", 2) is Nonerow = get_order(db, "bob", 2)assert row[2] == "緑茶" def test_alice_cannot_cancel_bob(db):with pytest.raises(NotFound):cancel(db, "alice", 2)assert status(db, 2) == "open"

試験は、利用者を 2 人用意し、互いの物に届かないことを両方向で固めます。自分の物は見えることも同時に確かめるので、何でも拒む壊れ方も見逃しません。OWASP の Cheat Sheet も、認可の誤りは小さくても重い結果になるとして、単体と結合の試験を書くよう勧めています。

番号をランダムな値に替えるのは、このあとに重ねる守りです。共同の注意喚起は、URL に ID を出さない間接の参照や、UUID のような強いランダムな値を勧めつつ、UUID は安全のための仕組みに使うべきではないとも注記しています。順番を間違えないでください。先に確かめ、そのうえで推測しにくくします。

関わる事例

この穴に合う事例は、事例の一覧にはまだありません。代わりに、この穴の種類について公的機関が出した文書を紹介します。

2023年7月27日、豪州通信電子局の豪州サイバーセキュリティセンター (ACSC)、米国の CISA、NSA は、IDOR についての共同の注意喚起 (AA23-208A) を出しました。文書は、IDOR を、ほかの正しい利用者の ID を指定した要求で、データの書き換え、削除、読み出しを許してしまうアクセス制御の穴だと説明しています。よくある穴で、開発の過程の外では防ぎにくく、大きな規模で悪用できるため、データの漏えいで頻繁に使われるとも書いています。

作り手への勧めは、このレッスンの直し方と同じ向きです。既定で拒み、データを変える、消す、慎重に扱うべきデータを読む、どの要求にも認証と認可の確かめを入れること、です。

確認クイズ

1 問目

注文の番号を連番から UUID に替えました。持ち主の確かめは入れていません。これで足りますか。

  • ア 足りる。UUID は推測できないので、他人の注文には届かない
  • イ 足りない。番号が漏れれば、持ち主の確かめが無いので同じように届く
  • ウ 足りる。UUID なら、記録に残る番号も推測できない
答えと理由を見る

答え イ

UUID は推測を難しくする重ねの守りで、確かめの代わりではありません。共有のリンク、記録、画面の写しなどから番号が漏れれば、誰でもその注文に届きます。共同の注意喚起も、UUID を安全のための仕組みに使うべきではないと注記しています。

2 問目

詳細を見る画面では持ち主を確かめています。取り消しの API は画面のボタンからしか呼ばれません。取り消しにも確かめは要りますか。

  • ア 要らない。ボタンは本人の注文にしか出ていない
  • イ 要らない。見る時に確かめているので、番号は本人の物だと分かっている
  • ウ 要る。要求は画面を通らずに直接送れるので、操作ごとに確かめる
答えと理由を見る

答え ウ

ボタンの出し分けは見た目の話で、サーバーへの要求は誰でも組み立てて送れます。見る時の確かめは、その要求の中でしか効きません。だから取り消しも、変更も、それぞれの要求の中で持ち主を確かめます。

説明課題

次のことを、自分の言葉で 3 行にまとめてください。書けたら、このレッスンは修了です。

  • なぜ認可は、ログインの時ではなく、すべての操作で要るのか。例を 1 つ挙げて
模範解答を見る

ログインで分かるのは「誰か」だけで、「その物を触ってよいか」は物と操作ごとに違う。たとえば注文の詳細では持ち主を確かめていても、取り消しの API が番号だけで動けば、番号を書き換えた要求で他人の注文を取り消せる。だから持ち主の確かめは 1 か所の関所にまとめ、すべての操作がそこを通るように作る。

一次資料

どれも 2026年10月24日に開いて、題と中身を確かめました。

次へ

次のレッスンは「サーバーを踏み台にした内部への到達」です。外の URL を取りに行く機能が、内側への入口になる穴を扱います。

次のレッスン 2-5 サーバーを踏み台にした内部への到達

このレッスンにつながる物