mishandling-exceptional-conditions について
OWASP A10:2025の例外条件の不適切な処理に焦点を当てた、コードレビューとテスト向けのセキュリティスキルです。PythonのWebアプリ向けチェックリスト、スキャン方法、修正の優先度を示し、クラッシュや情報漏えい、悪用可能な状態を扱います。
主な機能
- 01例外処理とグローバルハンドラーの確認
- 02認証ロジックのフェイルオープン検出
- 03エラーレスポンス内の機密情報を確認
- 04例外経路でのクリーンアップとロールバックを監査
- 05Banditとパターン検索によるスキャン
活用シーン
- 01FastAPIやFlaskアプリの例外処理をレビュー
- 02エラーに含まれるスタックトレースやDB情報の漏えいを発見
- 03認証失敗時に誤ってアクセスを許可しないか確認
- 04エラー経路のリソースリークやトランザクション問題を特定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ scholarly360/ owasp-top10-web-skills --skill mishandling-exceptional-conditionsインストール後、Claude Code で/mishandling-exceptional-conditionsを入力して読み込まれたか確認します。
詳細
OWASP A10:2025 — 例外状態の不適切な処理
2025年に新設されたカテゴリで、SSRF(A01に統合)に代わるものです。24件のCWEを含み、769,581件の記録された発生事例があります。異常な状況を防止、検出、対応できないプログラムを扱います。その結果、クラッシュ、予期しない動作、情報漏えい、悪用可能な状態につながります。
基本概念
対象範囲
| 弱点 | リスク |
|---|---|
| 不適切なエラー処理 | 未処理の例外によってアプリがクラッシュしたり、内部情報が漏えいしたりします |
| フェイルオープン(CWE-636) | 例外が発生した際に認証/認可チェックを通過させてしまいます |
| エラーメッセージ内の機密データ(CWE-209) | スタックトレース、DB の詳細、ファイルパスがクライアントに漏えいします |
| 捕捉されない例外(CWE-248) | グローバルなセーフティネットがなく、デバッグ出力を含む 500 エラーが返されます |
| 例外処理経路でのリソース枯渇 | エラー時に DB 接続やファイルハンドルが閉じられません |
| 状態の破損 | 例外発生時に部分的なトランザクションがコミットされます |
| NULL ポインター/None の参照外し(CWE-476) | 属性にアクセスする前に None を確認するガードがありません |
| デフォルトケースの欠落(CWE-754) | if/elif チェーンに else がない、または match にワイルドカードがありません |
| エラー復旧処理での競合状態 | クリーンアップ処理自体が中断される可能性があります |
防止の原則
- エラーが発生した場所で捕捉する — 3 層上ではなく、エラーを処理できる関数で捕捉します。
- フェイルクローズにする — 認証/認可のロジックで例外が発生した場合はアクセスを拒否し、決して通過させません。
- 完全にロールバックする — DB トランザクションの一部だけを残してはならず、必ずロールバックします。
- ユーザー向けメッセージと技術的な詳細を分離する — スタックトレースはサーバー側に記録し、クライアントには安全で一般的なメッセージを返します。
- グローバル例外ハンドラーをセーフティネットとして用意する — すべてのアプリに必要ですが、ローカルでの処理に代わるものではありません。
- レート制限とクォータを設定する — リソース枯渇などの例外的な状態が意図的に引き起こされるのを防ぎます。
FastAPI テストチェックリスト
1. グローバル例外ハンドラー
# REQUIRED — catches anything not handled locally
@app.exception_handler(Exception)
async def global_handler(request: Request, exc: Exception):
logger.error("Unhandled exception", exc_info=exc)
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
存在しない場合は指摘してください。 FastAPI のデフォルトの 500 レスポンスには、開発モードでトレースバックが含まれる場合があります。
2. フェイルクローズの認証ミドルウェア(CWE-636)
# BAD — exception causes pass-through (fail open)
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
try:
verify_token(request)
except Exception:
pass # ← VULNERABILITY
return await call_next(request)
# GOOD — exception causes denial (fail closed)
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
try:
verify_token(request)
except Exception as e:
logger.warning("Auth check failed", exc_info=e)
return JSONResponse(status_code=401, content={"detail": "Unauthorized"})
return await call_next(request)
3. カスタム HTTP エラーレスポンス(CWE-756 / CWE-209)
# Register handlers for all common error codes
@app.exception_handler(404)
async def not_found(request, exc):
return JSONResponse(status_code=404, content={"detail": "Not found"})
@app.exception_handler(422)
async def validation_error(request, exc):
return JSONResponse(status_code=422, content={"detail": "Invalid input"})
レスポンス本文で str(exc)、repr(exc)、または traceback.format_exc() を返すハンドラーは、すべて指摘してください。
4. 例外処理経路でのリソースのクリーンアップ
# BAD — connection leaked if query raises
conn = db.connect()
result = conn.execute(query) # raises → conn never closed
conn.close()
# GOOD — context manager guarantees cleanup
with db.connect() as conn:
result = conn.execute(query)
次を検索します:db.connect()、open(file)、socket.socket() が with ブロックまたは try/finally の中にないケース。
5. SQLAlchemy のトランザクションロールバック
# BAD — partial write committed on error
session.add(obj)
risky_operation() # raises → session still commits elsewhere
session.commit()
# GOOD — rollback on exception
try:
session.add(obj)
risky_operation()
session.commit()
except Exception:
session.rollback()
raise
except 分岐で対応する session.rollback() がない session.commit() はすべて指摘してください。
Flask テストチェックリスト
1. グローバル例外ハンドラー
@app.errorhandler(Exception)
def global_error(e):
app.logger.error("Unhandled exception", exc_info=e)
return {"error": "Internal server error"}, 500
個別のハンドラーも登録してください:
@app.errorhandler(404)
def not_found(e):
return {"error": "Not found"}, 404
@app.errorhandler(500)
def server_error(e):
return {"error": "Server error"}, 500
2. Flask デバッグモード — A02 と A10 の両方に該当する問題
デバッグモード(debug=True または FLASK_DEBUG=1)を有効にすると、Werkzeug の対話型デバッガーが有効になります。これにより、次のことが可能になります。
- 完全なトレースバックがブラウザーに公開されます。
- デバッガーコンソール経由で任意のコードを実行できます。
本番環境の設定または環境変数で見つかった場合は、直ちに指摘してください。
3. request.args.get() が暗黙的に None を返す問題
# BAD — returns None silently; downstream code may crash
user_id = request.args.get("user_id")
user = db.query(User).filter_by(id=user_id).first() # id=None → wrong query
# GOOD — explicit validation
user_id = request.args.get("user_id")
if user_id is None:
abort(400, description="user_id is required")
FastAPI の Pydantic モデルではこれが自動的に防止されますが、Flask では手動でガードする必要があります。
4. フェイルクローズの before_request フック
# BAD — exception in auth check bypasses protection
@app.before_request
def check_auth():
try:
verify_session()
except:
pass # ← VULNERABILITY
# GOOD
@app.before_request
def check_auth():
try:
verify_session()
except Exception as e:
app.logger.warning("Session verify failed", exc_info=e)
abort(401)
自動スキャンの方法
静的解析(Bandit)
bandit -r . -t B110,B112 # try-except-pass and try-except-continue
bandit -r . --severity-level medium
このカテゴリに該当する主な Bandit のテスト ID:
B110—try: ... except: pass(例外の握りつぶし)B112—try: ... except: continue(ループ内での例外の握りつぶし)
パターン検索(grep / ripgrep)
# Fail-open patterns
rg "except.*:\s*(pass|continue|return True)" --type py
# Stack traces leaking to response
rg "traceback\.format_exc\(\)|str\(exc\)|repr\(exc\)" --type py
# Missing context managers
rg "open\(|\.connect\(\)" --type py # review each hit for 'with'
# Missing rollback
rg "session\.commit\(\)" --type py # check each for paired rollback
ランタイム/DAST
- 意図的に 404、500、検証エラー(FastAPI では 422)を発生させます。
- HTTP レスポンスに、ファイルパス、スタックフレーム、SQL クエリ文字列、例外クラス名が含まれていないか確認します。
- 不正な Authorization ヘッダーを送信してミドルウェアを調査し、レスポンスが
200ではなく401であることを確認します。
主要な CWE(A10:2025)
| CWE | 名称 | Python での発現例 |
|---|---|---|
| CWE-209 | エラーメッセージ内の機密情報 | JSON レスポンス内の str(exc)、デバッグ用トレースバック |
| CWE-248 | 捕捉されない例外 | グローバルハンドラーがない場合、未処理の raise によってアプリがクラッシュします |
| CWE-636 | フェイルオープン | 認証チェック内の except: pass |
| CWE-703 | 例外条件の不適切な処理 | エラーを隠す広範な except |
| CWE-754 | 通常とは異なる条件の不適切なチェック | 外部入力に対する None/空のチェック不足 |
| CWE-476 | NULL ポインター参照のデリファレンス | None の可能性があるオブジェクトへの属性アクセス |
| CWE-756 | カスタムエラーページの欠如 | デバッグ情報を含むフレームワークのデフォルトエラーページ |
修正の優先順位
- 重大: フェイルオープンする認証ミドルウェア/
before_requestフック(CWE-636) — 直接的な認証バイパスにつながります。 - 重大: 本番環境でのデバッグモードの有効化(Werkzeug デバッガーによる RCE)。
- 高: HTTP レスポンス内のスタックトレース/DB 詳細情報(CWE-209) — 攻撃者の偵察に役立ちます。
- 高: グローバル例外ハンドラーの欠如 — 未処理の経路によってアプリがクラッシュし、詳細情報が漏えいする可能性があります。
- 中: トランザクションのロールバック不足 — データの整合性が損なわれます。
- 中: 例外発生時のリソースリーク — 攻撃を受けるとコネクションプールが枯渇します。
- 低:
request.args.get()がサイレントにNoneを返すこと — 直接悪用されるものではなく、ロジック上のバグにつながります。