broken-access-control について
PythonのWebアプリを対象に、アクセス制御の不備を調べるセキュリティ監査スキルです。認可漏れ、IDOR、CORS、SSRF、CSRF、JWTとセッションの安全性を扱い、監査手順とスキャン方法を示します。
主な機能
- 01ルートの認可と権限チェックの確認
- 02リソース所有権の検証によるIDOR検出
- 03CORSとSSRFリスクの確認
- 04CSRF、JWT、セッション設定の点検
- 05BanditとOWASP ZAPのスキャン案内
活用シーン
- 01FastAPIまたはFlaskアプリの監査
- 02ユーザーリソースへの不正アクセス確認
- 03CORS設定とユーザー指定URLのリスク調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scholarly360/ owasp-top10-web-skills --skill broken-access-controlインストール後、Claude Code で/broken-access-controlを入力して読み込まれたか確認します。
同梱ファイルreferences/bac-detail.md
詳細
アクセス制御の不備(A01:2025)
2回連続でOWASPリスクの第1位です。 アプリケーションの3.74%で発見され、180万件以上の発生と32,654件のCVEが報告されています。アクセス制御は、ユーザーが意図された権限の範囲を超えて操作できないようにします。不備があると、データの不正な開示、改ざん、破壊につながります。
すべてのCWEと修正パターンの詳しい資料については、
references/bac-detail.mdを参照してください。
このスキルの対象範囲
| サブカテゴリー | 説明 | 主なCWE |
|---|---|---|
| 認可の欠如 | 認証ガードのないルート/エンドポイント | CWE-862, CWE-284 |
| IDOR | リソースの所有者が検証されていない | CWE-639, CWE-285 |
| CORSの設定ミス | ワイルドカードまたは過度に許可的なオリジン | CWE-284 |
| SSRF | 内部/プライベートIPへのアウトバウンドリクエスト | CWE-918 |
| CSRF | 状態を変更するフォームにトークン検証がない | CWE-352 |
| 権限昇格 | ユーザーがより高い権限のロールにアクセスする | CWE-269 |
| JWT/セッションの改ざん | メタデータの改ざん、セッション固定化 | CWE-287 |
ワークフロー:FastAPIまたはFlaskアプリの監査
ステップ1 — すべてのルートを特定する
FastAPI:
# List all routes and their dependencies
for route in app.routes:
print(route.path, route.methods, route.dependencies)
Flask:
# Print all registered endpoints
for rule in app.url_map.iter_rules():
print(rule.endpoint, rule.methods, rule.rule)
次に該当するルートをすべて指摘してください。
- 認証依存関係なしでPOST、PUT、PATCH、DELETEを処理する
- 所有権を確認せずにユーザー固有のデータ(プロフィール、注文、ファイル)を返す
- 機密性の高い操作にレート制限がない
ステップ2 — 認可ガードを確認する
FastAPI — Depends()の欠落を確認する:
# VULNERABLE — no auth check
@app.get("/users/{user_id}/data")
async def get_user_data(user_id: int):
return db.query(UserData).filter_by(owner_id=user_id).all()
# SECURE — requires authenticated user
@app.get("/users/{user_id}/data")
async def get_user_data(user_id: int, current_user: User = Depends(get_current_user)):
if current_user.id != user_id:
raise HTTPException(status_code=403, detail="Forbidden")
return db.query(UserData).filter_by(owner_id=user_id).all()
Flask — @login_requiredの欠落を確認する:
# VULNERABLE
@app.route("/dashboard")
def dashboard():
return render_template("dashboard.html")
# SECURE
@app.route("/dashboard")
@login_required
def dashboard():
return render_template("dashboard.html")
ステップ3 — IDORチェック(リソースの所有権)
IDORは、BACの不備で最もよく見られるものです。ユーザーが所有するリソースを取得するすべてのクエリで、resource.owner_idとcurrent_user.idを照合する必要があります。
# VULNERABLE IDOR — attacker can request any document_id
@app.get("/documents/{doc_id}")
async def get_doc(doc_id: int, user=Depends(get_current_user)):
return db.get(Document, doc_id) # No ownership check!
# SECURE
@app.get("/documents/{doc_id}")
async def get_doc(doc_id: int, user=Depends(get_current_user)):
doc = db.get(Document, doc_id)
if not doc or doc.owner_id != user.id:
raise HTTPException(status_code=404) # 404 preferred over 403 (avoids enumeration)
return doc
テスト方法: ユーザーAとして認証し、リソースIDを記録した後、ユーザーBとして認証して同じIDをリクエストします。データを取得できた場合は、IDORです。
ステップ4 — CORSの設定ミス
FastAPI:
# VULNERABLE — wildcard origin
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True)
# SECURE — explicit allowlist
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.yourdomain.com"],
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["Authorization", "Content-Type"],
)
Flask:
# VULNERABLE
CORS(app, origins="*")
# SECURE
CORS(app, origins=["https://app.yourdomain.com"], supports_credentials=True)
⚠️
allow_origins=["*"]とallow_credentials=Trueの組み合わせは二重に危険です。多くのブラウザーはこれをブロックしますが、深刻な設定ミスを示しています。
ステップ5 — SSRF(A01:2025の対象に追加)
ユーザーが制御するURLを使ってアウトバウンドHTTPリクエストを送信するコードをすべて確認してください。
import ipaddress, re
PRIVATE_RANGES = [
ipaddress.ip_network("10.0.0.0/8"),
ipaddress.ip_network("172.16.0.0/12"),
ipaddress.ip_network("192.168.0.0/16"),
ipaddress.ip_network("127.0.0.0/8"),
ipaddress.ip_network("169.254.0.0/16"), # AWS metadata
]
def is_safe_url(url: str) -> bool:
from urllib.parse import urlparse
import socket
try:
host = urlparse(url).hostname
ip = ipaddress.ip_address(socket.gethostbyname(host))
return not any(ip in net for net in PRIVATE_RANGES)
except Exception:
return False
# In your endpoint:
@app.post("/fetch")
async def fetch_url(url: str, user=Depends(get_current_user)):
if not is_safe_url(url):
raise HTTPException(status_code=400, detail="URL not allowed")
return await httpx.get(url)
テスト方法: http://169.254.169.254/latest/meta-data/(AWSメタデータ)、http://localhost:6379(Redis)、http://10.0.0.1/admin
ステップ6 — CSRF(Flask固有のリスク)
JWT/Bearerトークンを使うFastAPI APIは、一般にCSRFの影響を受けません。セッションCookieを使うFlaskアプリには明示的な保護が必要です。
# Flask — verify Flask-WTF is installed and CSRF is enabled
from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtect(app)
# All state-changing forms must include {{ form.hidden_tag() }} or {{ csrf_token() }}
設定でWTF_CSRF_ENABLED = Trueになっていることを確認してください(デフォルトで有効ですが、無効化されていないことを確認してください)。
ステップ7 — JWTとセッションのセキュリティ
# Verify JWT expiry is set (short-lived: 15 min–1 hr)
jwt.encode({"sub": user_id, "exp": datetime.utcnow() + timedelta(minutes=30)}, SECRET_KEY)
# Flask — verify session security flags
app.config["SESSION_COOKIE_SECURE"] = True # HTTPS only
app.config["SESSION_COOKIE_HTTPONLY"] = True # No JS access
app.config["SESSION_COOKIE_SAMESITE"] = "Lax" # CSRF mitigation
app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(hours=2)
簡易監査チェックリスト
PythonのWebアプリを監査するときは、次の項目を確認してください。
- すべての非公開ルートに認証依存関係/デコレーターがある
- ユーザー所有リソースのクエリすべてで
resource.owner_id == current_user.idを検証する - CORSオリジンが
*ではなく、明示的な許可リストになっている - アウトバウンドHTTP呼び出しで、URLがプライベートIP範囲内でないことを検証する
- Flaskアプリの、状態を変更するすべてのフォームでFlask-WTFのCSRF保護が有効になっている
- JWTの
expクレームの有効期限が短く、セッションにSECURE、HTTPONLY、SAMESITEフラグが設定されている - ログアウト時にセッションIDが無効化される
- アクセス制御の不備がログに記録され、アラートが発報される
自動スキャン
| ツール | 検出対象 | コマンド |
|---|---|---|
| Bandit | SSRFのパターン、ハードコードされたシークレット、危険な関数 | bandit -r . -t B105,B106,B107,B501,B601 |
| OWASP ZAP | IDOR、実行時の認証の欠落、CORS | zap-baseline.py -t http://localhost:8000 |
| truffleHog | Gitの履歴に漏えいしたトークン/キー | trufflehog git file://. --only-verified |
主要な CWE リファレンス
| CWE | 名前 | 典型的なパターン |
|---|---|---|
| CWE-284 | 不適切なアクセス制御 | 認証なしでルートにアクセスできる |
| CWE-285 | 不適切な認可 | 認証はあるが、ロールや所有権を確認していない |
| CWE-352 | CSRF | 状態を変更するエンドポイントが Cookie 認証を受け付け、CSRF トークンを要求しない |
| CWE-639 | ユーザー制御キーによる認証のバイパス | IDOR — 所有権を確認せずに GET /orders/{id} を実行する |
| CWE-862 | 認可の欠落 | @login_required または Depends(get_current_user) がない |
| CWE-918 | SSRF | 検証せずに requests.get(user_input_url) を実行する |
| CWE-200 | 機密情報の漏えい | 認可されていないユーザーが他者の個人識別情報(PII)やデータを受け取る |
関連項目
references/bac-detail.md— CWE の詳細な説明、攻撃例の追加、コンプライアンスとの対応付け- OWASP A01:2025 公式ページ:https://owasp.org/Top10/A01_2025-Broken_Access_Control/