insecure-design について
insecure-design は、OWASP A06:2025 を対象に、アーキテクチャ上のセキュリティ制御の欠如や不備を検討するレビュー用スキルです。チェックリスト、STRIDE による脅威モデリング、修正の優先順位を用いて、レート制限やテナント分離などを確認します。
主な機能
- 01アーキテクチャ上の設計不備を特定
- 02レート制限とファイルアップロードを確認
- 03ビジネスロジックとテナント分離を評価
- 04サーバー側・クライアント側の制御を検討
- 05STRIDE による脅威モデリングを支援
活用シーン
- 01FastAPI または Flask アプリのセキュリティ設計をレビュー
- 02ログインや決済フローのレート制限を確認
- 03マルチテナントクエリの分離を評価
- 04実装前に STRIDE で脅威を整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scholarly360/ owasp-top10-web-skills --skill insecure-designインストール後、Claude Code で/insecure-designを入力して読み込まれたか確認します。
同梱ファイルreferences/cwe-details.mdreferences/threat-modeling.md
詳細
OWASP A06:2025 — 安全でない設計
このスキルの機能
安全でない設計の脆弱性の検出、分析、修正をガイドします。これは、セキュリティ制御が存在しない、または構造的に不十分なアーキテクチャレベルの欠陥です。実装上のバグとは異なり、安全でない設計はコードにパッチを適用するだけでは修正できません。フローを再設計するか、不足している制御レイヤーを追加する必要があります。
OWASP 2025 での位置付け: 6位(脅威モデリングの導入が進んだため、2021年の4位から低下)
対象 CWE: 39 | 平均発生率: 1.86%
主な CWE: CWE-434、CWE-269、CWE-362、CWE-799、CWE-602
主な概念
設計上の欠陥と実装上の欠陥
| 安全でない設計(A06) | 実装上のバグ(その他のカテゴリ) |
|---|---|
| ログインフローにレート制限が設計段階から組み込まれていない | レート制限機能はあるが、設定が誤っている |
| 設計上、ファイルアップロード先が Web ルート内になっている | アップロード処理に拡張子チェックがない |
| マルチテナント DB スキーマにテナント分離がない | クエリに WHERE 句がない |
| ビジネスロジックで負の数量が許可されている | サーバー側の入力検証がない |
設計上の欠陥には、既存のコードにパッチを適用するだけではなく、新たな制御の追加またはアーキテクチャのリファクタリングが必要です。
FastAPI / Flask のテストチェックリスト
1. レート制限(CWE-799)
ログイン、登録、パスワードリセット、高額取引のエンドポイントにレート制限が適用されていることを確認します。
FastAPI — SlowAPI:
from slowapi import Limiter
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
@app.post("/login")
@limiter.limit("5/minute")
async def login(request: Request, ...): ...
Flask — Flask-Limiter:
from flask_limiter import Limiter
limiter = Limiter(app, key_func=get_remote_address)
@app.route("/login", methods=["POST"])
@limiter.limit("5 per minute")
def login(): ...
レビューで注意すべき危険信号:
- レート制限デコレーターのないログイン、
/register、/reset-password、/checkoutのルート RATELIMIT_ENABLED = False、またはリミッターを初期化しているが、機密性の高いルートに適用していない- レート制限が緩すぎる(例:認証エンドポイントで
1000/minute)
2. ファイルアップロードのセキュリティ(CWE-434)
ファイルアップロードは拡張子だけでなくコンテンツを検証し、Web ルートの外部に保存する必要があります。
import magic # python-magic
ALLOWED_MIME = {"image/jpeg", "image/png", "application/pdf"}
MAX_SIZE = 5 * 1024 * 1024 # 5 MB
@app.post("/upload")
async def upload(file: UploadFile):
content = await file.read(MAX_SIZE + 1)
if len(content) > MAX_SIZE:
raise HTTPException(400, "File too large")
mime = magic.from_buffer(content[:2048], mime=True)
if mime not in ALLOWED_MIME:
raise HTTPException(400, "Unsupported file type")
# Store in /var/app/uploads/ — NOT inside static/ or public/
safe_path = UPLOAD_DIR / secrets.token_hex(16)
safe_path.write_bytes(content)
危険信号:
file.content_typeまたは拡張子だけに依存している(filename.endswith(".jpg"))static/、public/、その他 Web からアクセス可能なパスにアップロードを保存している- ファイルサイズの上限がない
- 認証なしで、直接 URL からアップロード済みファイルを配信している
3. ビジネスロジックの欠陥
ビジネスロジックの欠陥はアプリケーション固有のものであり、正規のフローを意図しない方法で悪用します。
よくあるテストパターン:
| シナリオ | テスト |
|---|---|
| カート内の負の数量 | quantity: -1 を指定して POST し、注文合計が負にならないことを確認する |
| 価格の改ざん | price フィールドを指定して POST し、サーバーがクライアントから送信された価格を無視することを確認する |
| 同時購入の競合 | 最後の1点を購入するリクエストを並列送信し、1件だけが成功することを確認する |
| 予約システムの悪用 | 同じ時間枠を並列で2回予約する |
| クーポンの再利用 | 最初の取引がコミットされる前に同じクーポンを2回適用する |
FastAPI の例 — 安全な数量処理:
class OrderItem(BaseModel):
product_id: int
quantity: int = Field(..., ge=1, le=100) # enforced server-side
危険信号:
- クライアントのペイロードから価格、割引、ロールのフィールドを受け入れている
- 金融取引に冪等性キーや重複チェックがない
- Pydantic または同等の仕組みで、数量や金額に
ge=1の下限が設定されていない
4. テナント分離(CWE-269)
マルチテナントアプリでは、すべての DB クエリを認証済みテナントの範囲に限定する必要があります。
適用すべきパターン:
# UNSAFE — returns data across all tenants
def get_reports(db: Session):
return db.query(Report).all()
# SAFE — scoped to current tenant
def get_reports(db: Session, current_user: User):
return db.query(Report).filter(Report.tenant_id == current_user.tenant_id).all()
危険信号:
- テナント/ユーザーのフィルターがない
db.query(Model).all()がある - 管理者向けのクエリに、ユーザー向けエンドポイントからアクセスできる
- リクエスト本文でテナント ID を渡している(ユーザー入力ではなく、JWT/セッションから取得する必要があります)
5. サーバー側セキュリティのクライアント側での適用(CWE-602)
ブラウザーでのみ適用されるセキュリティは、API を直接呼び出すことで簡単に回避されます。
危険信号:
- 認可チェックが JavaScript/フロントエンドにしかなく、API ルートにない
- 価格、ロール、アクセスレベルを制御する非表示のフォームフィールド
- クライアントから渡された値に基づく機能フラグ(JWT クレームまたはサーバーセッションを使用してください)
# WRONG — trusting client-supplied role
@app.post("/admin/action")
async def admin_action(role: str = Body(...)): # never do this
if role == "admin":
...
# CORRECT — derive from authenticated token
@app.post("/admin/action")
async def admin_action(current_user: User = Depends(get_current_user)):
if current_user.role != "admin":
raise HTTPException(403)
...
6. 認証情報/アカウント復旧の設計
安全でない復旧フローは、実装上のバグではなく設計上の欠陥です。
危険な兆候:
- 知識ベースの秘密の質問(母親の旧姓、ペットの名前など)— ソーシャルメディアから推測できます
- パスワードリセットリンクが15~60分以内に期限切れにならない
- リセットトークンがHTTP経由で送信される、またはハッシュ化せずに保存される
- リセットフローを通じてアカウントの列挙が可能(「アカウントが見つかりません」と表示する場合と、何も知らせず成功したように見せる場合がある)
安全なパターン:
# Generate short-lived, single-use, high-entropy token
token = secrets.token_urlsafe(32)
expiry = datetime.utcnow() + timedelta(minutes=30)
# Store hash of token, not the token itself
db_token = PasswordResetToken(
user_id=user.id,
token_hash=hashlib.sha256(token.encode()).hexdigest(),
expires_at=expiry,
used=False
)
脅威モデリングのガイダンス(STRIDE)
STRIDEを使って、設計上の欠陥がコードに反映される前に見つけましょう。
| 脅威 | 対象 | FastAPI/Flaskでの例 |
|---|---|---|
| Spoofing(なりすまし) | 認証フロー | 予測可能なセッショントークン、MFAの欠如 |
| Tampering(改ざん) | 転送中/保存中のデータ | 署名付きデータに対するHMACの欠如、書き込み可能なアップロード |
| Repudiation(否認) | 監査ログ | 金融操作に関するリクエストログの欠如 |
| Information Disclosure(情報漏えい) | エラーメッセージ、APIドキュメント | 本番環境で/docsが公開されている、500エラーでスタックトレースが表示される |
| Denial of Service(サービス拒否) | レート制限、リソース使用量 | リクエストサイズの上限がない、レートリミッターの欠如 |
| Elevation of Privilege(権限昇格) | 認可設計 | クライアントが指定したロール、テナントスコープの欠如 |
修正の優先度マトリクス
| 検出事項 | 深刻度 | 修正の複雑さ |
|---|---|---|
| 認証/決済エンドポイントにレート制限がない | 高 | 低 — SlowAPI/Flask-Limiterを追加 |
| ファイルアップロードがWebルートに保存される | 高 | 中 — 保存パスを変更し、配信レイヤーを追加 |
| クライアントが指定した価格/ロールのフィールド | 致命的 | 低 — リクエストモデルからフィールドを削除 |
| DBクエリにテナントスコープがない | 致命的 | 中 — すべてのクエリを監査 |
| 知識ベースの秘密の質問 | 中 | 高 — 復旧フローを再設計 |
| 在庫/予約における競合状態 | 中 | 高 — DBレベルのロックまたは冪等性を追加 |
参考資料
詳しくは、以下を参照してください。
references/cwe-details.md— A06に含まれる39件すべてのCWEの詳細な説明と例references/threat-modeling.md— FastAPI/Flaskアプリ向けSTRIDEワークシートのテンプレート
設計上の問題と重なる、関連するOWASPカテゴリ:
- A01 Broken Access Control — 設計によりデフォルトで過剰なアクセス権が付与される場合
- A07 Authentication Failures — 認証フローの設計が根本原因である場合
- A10 Mishandling of Exceptional Conditions — エラー復旧の設計でフェイルクローズにできていない場合