A01 アクセス制御の不備
認証は誰かを決め、認可はしてよいかを決める。A01 は、認可がサーバーの側で、要求のたびに、対象の1件ごとに効いていないときに起きる。
要点
- 判定は、攻撃者が書き換えられないサーバー側のコードで行う。公開の資源以外は既定で拒否する。
- 判定の材料のうち、要求の側が決められるものを信じた時点で穴になる。URLのID、隠しフィールド、端末の画面の制御、検証していないトークンの中身。
- 型は4つに分けて見る。対象の認可が無い IDOR、機能の認可が無い強制ブラウズ、本人の権限を借りる CSRF、サーバーの位置を借りる SSRF。
- 判定は1か所で作って全部の入口で使う。データの問い合わせそのものに持ち主の条件を入れる。
成立の条件
認可の判定は、主体、操作、対象の3つの組で決まる。どれか1つでも要求の側の言い分で決まると、他人の権限で動ける。
- 要求の側が決められる値を判定に使う パスやクエリのID、隠しフィールド、Cookie、検証しないトークンの中身。OWASP は JWT と Cookie と隠しフィールドの書き換えを典型に挙げる[1]。
- サーバーがその値を主体の権限と突き合わせない ログインしているかだけを見て、その記録の持ち主かは見ない。
- 判定が全部の入口に掛かっていない 別のメソッド、古い版のAPI、管理用の経路、静的ファイル。チートシートは静的な資源にも認可を掛けるよう求める[2]。
型1 対象の認可が無い IDOR
記録を指す値を差し替えるだけで他人の記録に届く。IDOR のチートシートは、対象、それを指す値、対象ごとの認可の欠落の3つがそろうと成り立つと書く[3]。
悪い例。架空の請求書のAPI
app.get('/invoices/:id', requireLogin, async (req, res) => {
const inv = await db.invoices.findOne({ id: req.params.id });
res.json(inv);
});
直した例。持ち主の条件を問い合わせに入れる
app.get('/invoices/:id', requireLogin, async (req, res) => {
const inv = await db.invoices.findOne({
id: req.params.id,
tenantId: req.user.tenantId,
ownerId: req.user.id,
});
if (!inv) return res.status(404).end(); // 他人の記録か存在しないかを区別させない
res.json(inv);
});
- 読んでから持ち主を比べる書き方より、問い合わせに条件を入れるほうが、一覧、更新、削除の全部に同じ条件が掛かりやすい。OWASP は、記録の持ち主を強制するモデルを求める[1]。
- IDを UUID にしても直らない。推測しにくくなるだけで、値が漏れれば届く。チートシートも、推測された場合や漏れた場合でも認可で止めるよう求める[2][3]。
- IDはパスだけでなく、本文、隠しフィールド、ヘッダ、GraphQL の引数、一括操作の配列の中にも来る。
型2 機能の認可が無い強制ブラウズ
管理の機能を画面で隠し、APIの側で役割を確かめない。OWASP の例は、端末のJavaScriptで止めているだけの管理画面へ、コマンドから直接要求を送るものだ[1]。
悪い例。経路ごとに確かめを付ける
router.get('/admin/users', requireRole('admin'), listUsers);
router.delete('/admin/users/:id', deleteUser); // 付け忘れ。既定で通る
直した例。表に無い経路は拒否し、判定を1か所に置く
const policy = {
'GET /admin/users': ['admin'],
'DELETE /admin/users/:id': ['admin'],
};
app.use((req, res, next) => {
const roles = policy[routeKeyOf(req)]; // 照合した経路の型。例 DELETE /admin/users/:id
if (!roles || !roles.includes(req.user?.role)) return res.status(403).end();
next();
});
- 役割の名前を、署名を確かめていないトークンや Cookie から読まない。役割はサーバー側の記録から引く。署名の確かめ方は A04 で扱う。
- 同じ資源の全部のメソッドと、古い版のAPI、管理用のホスト名、ファイルの直接の配信に同じ表を掛ける。
- 役割だけでは足りない場面が多い。チートシートは、役割だけで決める RBAC より、属性や関係で決める ABAC や ReBAC を勧める[2]。
型3 本人の権限を借りる CSRF
利用者がログインしたまま罠のページを開くと、端末が Cookie を付けて、状態を変える要求を正規のサイトへ送る。サーバーから見れば本人の要求と区別が付かない。CWE-352 として A01 に入っている[1]。
直した例。状態を変えるメソッドに一括で掛ける
function csrfGuard(req, res, next) {
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next(); // GET で状態を変えない前提
const site = req.get('Sec-Fetch-Site');
if (site && site !== 'same-origin') return res.status(403).end();
if (!site && req.get('Origin') !== 'https://app.example.com') return res.status(403).end();
if (!tokenMatches(req.get('X-CSRF-Token'), req.session.csrfToken)) return res.status(403).end();
next();
}
- チートシートの推奨は、状態を持つ作りなら同期トークン、持たない作りなら署名してセッションに結び付けた二重送信 Cookie。新しい端末だけを相手にするなら Fetch Metadata の
Sec-Fetch-Siteで他サイトからの要求を落とせるが、送らない端末のために Origin の確認へ落とす道が要る[4][10]。 - Cookie の SameSite 属性は多層の守りとして足す。単独で頼らない[4]。
- GET で状態を変える経路があると、上の守りが全部外れる。
型4 サーバーの位置を借りる SSRF
URLを受け取ってサーバーが取りに行く機能で、行き先を利用者が決められる。サーバーは内側の網に居るので、外から届かない管理画面やクラウドのメタデータの窓口に要求を出させられる。2021年版では A10 として独立していた。
悪い例
const r = await fetch(req.body.imageUrl); // 行き先も手順も転送も利用者次第
直した例。行き先が決まっている場合は許可の一覧で決め、転送を追わない
const ALLOWED_HOSTS = new Set(['images.example.com']);
const u = new URL(req.body.imageUrl);
if (u.protocol !== 'https:' || !ALLOWED_HOSTS.has(u.hostname)) return res.status(400).end();
const r = await fetch(u, { redirect: 'error' }); // 転送先で一覧を迂回させない
- チートシートは、行き先が決まる場合と任意の外部へ出す場合を分ける。決まる場合は許可の一覧で、任意の場合は名前を引いた先のIPが許す網か確かめ、確かめたIPへそのまま接続する。確かめと接続の間に名前を引き直すと、DNS rebinding で抜けられる[5]。
- 文字列の否定の一覧で
localhostや内側のアドレスを弾く作りは、URLの解釈の違いで抜けられる。チートシートは許可の一覧を求める[5]。 - 網の層でも、アプリのサーバーが出てよい先だけを通す。AWS では IMDSv2 を必須にし、IMDSv1 を止める[5][11]。
- XML の外部実体参照からも SSRF が起きる。A02 の XXE を合わせて見る。
守り方
IPA の「安全なウェブサイトの作り方」も、脆弱性ごとに原因そのものをなくす根本的な解決策と、影響を減らす策を分けて示している[8]。
診断で見る所
許可を得た範囲で行う。範囲の決め方は 診断の許可と範囲 にある。試験の手順は WSTG の認可の試験と、ASVS 5.0 の V8 Authorization が詳しい[6][7]。
| 観点 | 用意と試し方 | 当たりの兆候 |
|---|---|---|
| 対象の認可 | 同じ役割の利用者AとB、別の組織の利用者Cを用意し、Aの要求のIDをBとCのものに替える。パス、クエリ、本文、ヘッダ、一括操作の配列の全部 | 200 で他人の中身が返る。更新が通る。一覧に他人の行が混ざる |
| 機能の認可 | 管理者で記録した要求を、一般の利用者と未ログインで送り直す。全部のメソッドと古い版のAPI | 403 か 401 でなく処理される |
| 判定の材料 | 役割や組織のIDが、Cookie、隠しフィールド、トークンの中身にあるか探し、書き換える | 書き換えた値で権限が変わる |
| CSRF | 状態を変える要求から、トークンと Origin を外す。GET で状態が変わる経路を探す | 外しても受理される |
| SSRF | URL、ホスト名、Webhook、取り込み、PDFの生成を受け取る引数を洗い出す。範囲の中の、自分が管理する受け口へ向けて、サーバーから要求が来るか見る | サーバーから受け口へ要求が来る。転送を追う |
| 置き忘れ | ディレクトリの一覧、.git、バックアップ、一時ファイル | 公開の場所から取れる |
コードのレビューでは、データを読む関数を全部挙げ、それぞれに主体の条件が入っているかを1つずつ見る。自動の走査は対象の認可をほとんど見つけられない。業務の上で誰の記録かを知らないからだ。
関連するCWE
A01 に対応する40のうち、設計とレビューでよく当たるもの[1][9]。
| CWE | 名前 | この頁の型 |
|---|---|---|
| CWE-284 | Improper Access Control | 全体の親 |
| CWE-862 | Missing Authorization | 型1、型2 |
| CWE-863 | Incorrect Authorization | 型1、型2 |
| CWE-639 | Authorization Bypass Through User-Controlled Key | 型1 |
| CWE-566 | Authorization Bypass Through User-Controlled SQL Primary Key | 型1 |
| CWE-425 | Direct Request, Forced Browsing | 型2 |
| CWE-352 | Cross-Site Request Forgery | 型3 |
| CWE-1275 | Sensitive Cookie with Improper SameSite Attribute | 型3 |
| CWE-918 | Server-Side Request Forgery | 型4 |
| CWE-22 | Path Traversal | ファイル名を受け取る機能 |
| CWE-601 | URL Redirection to Untrusted Site | ログイン後の戻り先 |
| CWE-548 | Exposure of Information Through Directory Listing | 置き忘れ |
参照
- A01:2025 Broken Access Control OWASP Top 10:2025
- Authorization Cheat Sheet OWASP Cheat Sheet Series
- Insecure Direct Object Reference Prevention Cheat Sheet OWASP Cheat Sheet Series
- Cross-Site Request Forgery Prevention Cheat Sheet OWASP Cheat Sheet Series
- Server-Side Request Forgery Prevention Cheat Sheet OWASP Cheat Sheet Series
- Authorization Testing OWASP Web Security Testing Guide
- V8 Authorization OWASP Application Security Verification Standard 5.0
- 安全なウェブサイトの作り方 改訂第7版 IPA 情報処理推進機構
- Common Weakness Enumeration MITRE
- Fetch Metadata Request Headers W3C
- Use the Instance Metadata Service to access instance metadata Amazon Web Services