关于 mishandling-exceptional-conditions
面向代码审查与安全测试的 OWASP A10:2025 安全技能,聚焦异常条件处理不当造成的崩溃、信息泄露和可利用状态。提供针对 Python Web 应用的检查清单、扫描方法和修复优先级。
主要功能
- 01检查异常处理与全局处理器
- 02识别认证逻辑中的故障放行
- 03检查错误响应中的敏感信息
- 04审查异常路径的资源清理与回滚
- 05使用 Bandit 和模式搜索辅助扫描
适用场景
- 01审查 FastAPI 或 Flask 应用的异常处理
- 02排查错误响应泄露堆栈或数据库信息
- 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) | 向客户端泄露堆栈跟踪、数据库详情和文件路径 |
| 未捕获的异常(CWE-248) | 缺少全局安全兜底机制 → 返回包含调试输出的 500 错误 |
| 异常处理路径中的资源耗尽 | 出错时数据库连接、文件句柄未关闭 |
| 状态损坏 | 异常发生时提交了部分事务 |
| NULL 指针/None 解引用(CWE-476) | 访问属性前未检查 None |
| 缺少默认分支(CWE-754) | if/elif 链没有 else;match 没有通配分支 |
| 错误恢复过程中的竞态条件 | 清理逻辑本身可能被中断 |
预防原则
- 在错误发生处捕获错误——由能够处理错误的函数捕获,而不是上溯三层才处理。
- 默认拒绝——身份验证/授权逻辑抛出异常时,拒绝访问;绝不放行。
- 彻底回滚——必须回滚部分数据库事务,绝不能提交。
- 将面向用户的消息与技术详情分开——在服务器端记录堆栈跟踪;向客户端返回安全、通用的消息。
- 将全局异常处理器作为安全兜底机制——每个应用都需要,但它不能替代局部处理。
- 限制速率和配额——防止有人故意触发异常情况(例如资源耗尽)。
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)
检查以下调用是否未放在 with 块或 try/finally 中:db.connect()、open(file)、socket.socket()。
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 标头来探测中间件——确认响应为
401,而不是200。
关键 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 | 空指针解引用 | 访问可能为 None 的对象的属性 |
| CWE-756 | 缺少自定义错误页面 | 使用包含调试信息的框架默认错误页面 |
修复优先级
- 严重: 失败时默认放行的身份验证中间件 /
before_request钩子(CWE-636)——可直接绕过身份验证。 - 严重: 在生产环境中启用调试模式(Werkzeug 调试器 = RCE)。
- 高: 在 HTTP 响应中泄露堆栈跟踪 / 数据库详细信息(CWE-209)——有助于攻击者侦察。
- 高: 缺少全局异常处理程序——未处理的路径会导致应用崩溃,并可能泄露详细信息。
- 中: 缺少事务回滚——会破坏数据完整性。
- 中: 异常路径中的资源泄漏——遭受攻击时会耗尽连接池。
- 低:
request.args.get()静默返回None——会导致逻辑错误,但无法直接利用。
评论
支持 Markdown