コース目次 / 第3章
直す — 集中管理と、既定拒否
水平・垂直の穴を、認可チェックの集中管理とdeny-by-defaultで塞ぎます。資源を返す直前に所有者/ロールを確認し、同じ攻撃で再検証します。設計の原則まで畳みます。
前の2章で開けた穴——水平(/orders/1002)と垂直(/admin/users)——を、両方塞ぎます。vuln-portal.js を直していきます。
法と倫理ゲート
手を動かす前に
このコースは攻撃の技術に触れます。実習してよい相手は、次の3つだけです。
- (a) 自分で作った練習用のやられ環境(このサイトが配る自作ターゲット)
- (b) あなた自身の許可された環境(自分の Thousand SKY・secret-notes-ctf など)
- (c) CTF 競技の中(規約の範囲で)
実在する第三者のシステムを許可なく触ることは、不正アクセス禁止法に触れます。 このゲートは、その線引きを毎回の入口で確かめるためのものです。詳しくは「セキュリティの法と倫理」へ。
直し1 — 水平: 所有者を確かめる
/orders/:id に、旗艦の IDOR と同じ所有者チェックを入れます。資源を返す直前で、その注文が本人のものかを確かめ、違えば 403。
const order = ORDERS.find((o) => o.id === Number(m[1]));
if (!order) { send(res, 404, { error: 'ありません' }); return; }
// 直したあと(水平): 本人の注文でなければ 403。
if (order.owner !== uid) { send(res, 403, { error: 'この注文を見る権限がありません' }); return; }
send(res, 200, order);
直し2 — 垂直: ロールを確かめる
/admin/users に、ロールチェックを入れます。管理者でなければ 403。
if (url.pathname === '/admin/users') {
if (!uid) { send(res, 401, { error: 'ログインしてください' }); return; }
// 直したあと(垂直): 管理者ロールでなければ 403。
if (USERS[uid].role !== 'admin') { send(res, 403, { error: '管理者権限が必要です' }); return; }
send(res, 200, Object.entries(USERS).map(([id, u]) => ({ id, ...u })));
return;
}
再検証 — 同じ攻撃を、両方
サーバを再起動して、前2章の攻撃をそのまま繰り返します。
http://127.0.0.1:3600/login?as=t1
http://127.0.0.1:3600/orders/1002 → 403(この注文を見る権限がありません)
http://127.0.0.1:3600/admin/users → 403(管理者権限が必要です)
どちらも 403 で弾かれます。念のため、正規の動きも確かめます——太郎は自分の注文 /orders/1001 は見られる、管理者(/login?as=admin)なら /admin/users が見られる。権限のある人だけが通る、正しい状態です。
ここで満足しない — 設計に畳む
エンドポイントごとに if を足して回るのは、直しとしては正しいのですが、危うさが残ります。
新しいエンドポイントを足すたびに、開発者が認可チェックを書き忘れないことに頼っている。人は忘れます。実際、この穴はまさに「一覧では書いたのに、詳細で忘れた」「ログインは見たのに、ロールを忘れた」という書き忘れでした。if を足して回るだけでは、次のエンドポイントでまた忘れます。
だから、設計を2つの原則に寄せます。
1. 集中管理(centralize)。認可チェックを各エンドポイントに散らさず、一箇所(共通のミドルウェアや、資源取得の関数)に集める。「注文を取得する」関数が、必ず所有者チェックを通す。個別の if に頼らない。
2. 既定は拒否(deny-by-default)。「明示的に許可された人以外は、すべて拒否」を既定にする。新しいエンドポイントは、何も書かなければアクセスできない状態から始める。許可を“足し忘れる”と動かないので気づく——逆に、拒否を“足し忘れる”と穴が空く今の作りより、はるかに安全。
つまり、「うっかり通してしまう」設計から、「うっかり止まる」設計へ。認可は、書き忘れが即・穴になる領域だからこそ、個人の注意力ではなく仕組みで守ります。
ロールが増えたら — RBAC / ABAC
権限が「一般/管理者」の2つで済むうちは単純ですが、現実は「編集者」「閲覧のみ」「経理だけ」…と増えます。
RBAC(ロールベース) — 権限をロールにまとめ、「このロールはこの操作を許す」を表で管理する。人ではなくロールに権限を紐づけるので、見通しがよい。
ABAC(属性ベース) — 「本人が所有者なら」「営業時間内なら」のように、属性や条件で許可を決める。柔軟だが複雑。
どちらでも芯は同じ——許可のルールを一箇所にまとめ、資源/機能を出す前に必ず通す。散らばった if から、集中したルールへ。
持ち帰る一言
認可は、集中して・既定は拒否。 資源を返す直前で所有者とロールを確かめ、そのチェックを一箇所に集め、何も書かなければ拒否される設計にする。個人の「書き忘れないこと」に頼らない。これで水平・垂直の両方を根から塞げます。次はまとめて、サーバサイドの深い穴へ送り出します。
こうなっていればOK
卒業まであと1章です。
この章はまだ完了していません。
保存できませんでした(プライベートブラウズ中かもしれません)。この端末に進捗は残りませんが、先へは進めます。