A06 安全性を欠いた設計
コードは書いた通りに動き、書いたことが間違っている。守るべき不変条件と信頼の境界を設計で決めていなければ、実装をどれだけ丁寧にしても直らない。
要点
- 実装の不具合とは根の原因も直し方も違う。設計に無い守りは、コードの修正では足せない[1]。
- 値段、合計、権限、持ち主、状態は、サーバーで導き直す。クライアントから受け取るのは利用者の意図だけ。
- 複数の手順は、サーバーの状態機械として遷移ごとに確かめる。画面の順番は守りにならない。
- 読んで、判断して、書く処理は競合状態になり得る。条件付きの更新か行ロックで1つの操作にする。
- 直し方の入口は脅威モデリングと、誤用の場面を試験にすること。
成立の条件
A06は、守りの仕組みが設計に無いか、あっても効かない状態を指す。業務の流れの欠陥、つまり起きてはならない状態の変化を定めていないことも含む[1]。次の2つがそろうと通る。
- 境界の誤り 利用者が書き換えられる場所にある値を、サーバーが判断に使う。
- 不変条件の欠落 1枚のクーポンは1回だけ、在庫を超えて売らない、支払いの前に確定しない、のような規則が、コードのどこでも強制されていない。
クライアントが決めた値を信用する
隠しフィールド、Cookie、JSONの本文、画面の非活性のボタンは、すべて利用者が書き換えられる。値段、小計、税、合計、割引をクライアントから受け取らない[2]。クライアントの側でだけ確かめる作りは CWE-602 に当たる。
悪い例と直した例。架空の通販の注文API
// 悪い例 送られてきた値段と合計をそのまま使う
const { itemId, qty, price, total } = req.body;
await charge(user, total);
// 直した例 受け取るのは商品と数量だけ。値段は商品の表から引き、合計はサーバーで計算する
const { itemId, qty } = req.body;
if (!Number.isInteger(qty) || qty < 1 || qty > MAX_QTY) throw new BadRequest();
const item = await db.one("SELECT price, stock FROM items WHERE id = $1", [itemId]);
if (qty > item.stock) throw new Conflict();
await charge(user, item.price * qty);
負の数量、0、極端に大きい数、小数も確かめる。数量が負なら合計が減る。業務の意味として正しい範囲かを確かめる検証で、形だけの検証とは別に要る[2]。
手順を飛ばす
画面が住所、支払い、確定の順に進んでも、APIは順番を知らない。確定のエンドポイントを直接呼ばれれば、支払いを経ずに注文が確定する。手順はサーバーの状態機械にし、遷移のたびに今の状態を確かめる[2]。CWE-841 に当たる。
// 直した例 許す遷移を表にし、更新の条件に今の状態を入れる
const NEXT = { cart: ["address"], address: ["paid"], paid: ["confirmed"] };
if (!NEXT[order.state]?.includes(to)) throw new Conflict();
const r = await db.query(
"UPDATE orders SET state = $1 WHERE id = $2 AND state = $3", [to, order.id, order.state]);
if (r.rowCount !== 1) throw new Conflict(); // 間に別の要求が状態を変えた
競合状態
値を読み、判断し、書く処理は、どれも競合状態になり得る[2]。1回限りのクーポン、残高、在庫、招待の枠で、同時に送った要求が両方とも判断を通る。CWE-362 に当たる。
// 悪い例 読む、判断する、書くが別々の文
const c = await db.one("SELECT used FROM coupons WHERE code = $1", [code]);
if (c.used) throw new Conflict();
await applyDiscount(order, code);
await db.query("UPDATE coupons SET used = true WHERE code = $1", [code]);
// 直した例 判断を更新の条件に入れ、注文の更新と同じトランザクションにする
await db.tx(async (t) => {
const r = await t.query(
"UPDATE coupons SET used = true WHERE code = $1 AND used = false", [code]);
if (r.rowCount !== 1) throw new Conflict();
await applyDiscount(order, code, t);
});
条件付きの更新で書けないときは、SELECT ... FOR UPDATE の行ロックか、一意の制約で二重の登録を拒む。アプリの中のロックは、サーバーが複数台になった時点で効かなくなる。
回数と量の上限が無い
機能の正しい使い方を、機械で何千回も繰り返されることを設計で想定していない状態。OWASPは、15人までの団体予約を手付金なしで受ける映画館で、数回の要求で全館の数百席を押さえられる例を挙げる[1]。CWE-799 に当たる。
- 利用者ごと、取引先ごと、送信元ごとの上限を、機能ごとに決める。紹介の特典、クーポン、新規登録の特典、予約の仮押さえは、特に悪用されやすい[2]。
- 仮押さえには期限を付け、期限で自動に解く。
- 発売の直後の数秒で買う、のような業務の上でありえない振る舞いを、検知と拒否の規則にする[1]。
守り方
次は緩和の策で、根本の直し方を済ませたうえで重ねる。
- 値を配る操作、たとえば返金、ポイントの付与、特典の発行をすべて記録し、量の急な変化で警告を出す[2]。
- 不変条件、同時の実行、手順の順番を確かめる結合試験を置く。正しい使い方と誤用の両方の場面で書く[1]。
診断で見る所
- 要求の本文の項目 値段、合計、権限、持ち主のID、状態のように、サーバーが自分で持っているはずの値が要求に入っていないか。入っていれば書き換えて送る。
- 手順のエンドポイント 複数の手順の最後のAPIを、前の手順を経ずに呼ぶ。戻る、同じ手順を2回送る、別の注文の手順と混ぜる。
- 同時の送信 1回限りの操作を、同じ要求を同時に送って試す。コードでは、
SELECTの結果で判断してからUPDATEする並びを探す。 - 境界の値 0、負の数、小数、上限を1つ超える値、非常に大きい数。
- 試験の手引き OWASP WSTG の業務ロジックの試験の章が、データの検証、要求の偽造、整合性、処理の時間、機能の回数の制限、手順の回避の試し方を並べる[7]。
関連するCWE
A06には39個のCWEが対応づけられている[1]。このページの節に関わるものを挙げる。手順と競合状態の例は、MITREの各ページにもある[8][9]。
| CWE | 名前 | このページの節 |
|---|---|---|
| CWE-501 | Trust Boundary Violation | 成立の条件 |
| CWE-602 | Client-Side Enforcement of Server-Side Security | クライアントが決めた値 |
| CWE-472 | External Control of Assumed-Immutable Web Parameter | クライアントが決めた値 |
| CWE-642 | External Control of Critical State Data | クライアントが決めた値 |
| CWE-841 | Improper Enforcement of Behavioral Workflow | 手順を飛ばす |
| CWE-362 | Concurrent Execution Using Shared Resource with Improper Synchronization | 競合状態 |
| CWE-799 | Improper Control of Interaction Frequency | 回数と量の上限 |
| CWE-653 | Insufficient Compartmentalization | テナントの分離 |
| CWE-657 | Violation of Secure Design Principles | 全体 |
参照
- A06:2025 Insecure Design OWASP Top 10:2025
- Business Logic Security Cheat Sheet OWASP Cheat Sheet Series
- Secure Product Design Cheat Sheet OWASP Cheat Sheet Series
- Threat Modeling Cheat Sheet OWASP Cheat Sheet Series
- Threat Modeling Manifesto Threat Modeling Manifesto Working Group
- Abuse Case Cheat Sheet OWASP Cheat Sheet Series
- Business Logic Testing OWASP Web Security Testing Guide
- CWE-841 MITRE CWE
- CWE-362 MITRE CWE