关于 authentication-failures
Authentication Failures 是面向 FastAPI 和 Flask 应用的认证安全审查技能,依据 OWASP A07:2025 检查常见认证与会话风险。它提供逐项测试清单、扫描命令和按优先级排列的修复方向。
主要功能
- 01检查 JWT 配置与声明验证
- 02审查认证端点的速率限制
- 03检查密码策略及泄露密码筛查
- 04审查会话管理与错误信息
- 05提供自动扫描命令和修复优先级
适用场景
- 01审查 FastAPI 或 Flask 登录流程
- 02检查 JWT 验证与会话失效设置
- 03排查硬编码凭据和弱密码策略
- 04按风险优先级规划认证问题修复
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ scholarly360/ owasp-top10-web-skills --skill authentication-failures安装后在 Claude Code 中输入/authentication-failures确认已加载。
详细介绍
身份验证失败 — OWASP A07:2025
排名: 第 7 位(稳定) | 涵盖的 CWE: 36 | 发生次数: 110 万+
2025 年新威胁: 混合密码攻击/密码喷洒——攻击者会递增使用泄露的凭据(例如,Password1! → Password2!)。
本技能涵盖的内容
当应用的身份验证机制缺失、薄弱或可被绕过时,就会发生身份验证失败。主要攻击模式:
- 凭据填充/暴力破解——使用泄露的凭据自动尝试登录
- 弱密码/默认密码——账户使用容易猜到或已知遭泄露的密码创建
- 不安全的凭据恢复——密码重置流程会泄露信息或可被绕过
- 缺少 MFA 或 MFA 可被绕过——没有第二重验证,或 MFA 可以跳过
- 会话标识符暴露——令牌出现在 URL、日志或引用来源标头中
- 会话固定——攻击者在身份验证前设置一个已知的会话 ID
- 会话失效不当——用户退出登录或撤销令牌后,会话仍然有效
FastAPI 和 Flask 测试清单
逐项检查每个部分。适用时标记 ✓ 通过、✗ 失败;不适用时标记 N/A。
1. JWT 实现
# ✓ GOOD — short-lived tokens, claim validation
token = jwt.encode(
{"sub": user_id, "exp": datetime.utcnow() + timedelta(minutes=15),
"iss": "myapp", "aud": "myapp-client"},
settings.SECRET_KEY, algorithm="HS256"
)
jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"],
audience="myapp-client", issuer="myapp")
# ✗ BAD — no expiry, no claim validation, accepts any algorithm
jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256", "RS256", "none"])
检查项:
- 访问令牌在 15–30 分钟后过期
- 刷新令牌在使用时轮换,并在退出登录时失效
- 设置
aud(受众)和iss(签发者)声明,并在解码时验证 - 算法固定为明确的列表——绝不包含
"none" - 从
os.environ加载 JWT 密钥,长度至少为 32 个字符 - 阻止算法混淆攻击:绝不接受将 RS256 密钥作为 HS256 密钥
2. 对身份验证端点进行速率限制
# 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") # ✓ per-IP rate limit
async def login(request: Request, credentials: LoginSchema):
...
# Flask — Flask-Limiter
from flask_limiter import Limiter
limiter = Limiter(app, key_func=get_remote_address, default_limits=["200/day"])
@app.route("/login", methods=["POST"])
@limiter.limit("5 per minute") # ✓
def login():
...
检查项:
- 对登录端点进行速率限制(每个 IP 每分钟 ≤5–10 次尝试)
- 多次失败后锁定账户(而非仅按 IP 限制)
- 注册和密码重置端点也进行速率限制
- 速率限制响应返回
429 Too Many Requests,而不是401
3. 密码策略与泄露密码检查
# ✓ GOOD — check against breached databases
import hashlib, httpx
def is_pwned(password: str) -> bool:
sha1 = hashlib.sha1(password.encode()).hexdigest().upper()
prefix, suffix = sha1[:5], sha1[5:]
resp = httpx.get(f"https://api.pwnedpasswords.com/range/{prefix}")
return suffix in resp.text
# ✓ GOOD — enforce minimum complexity
def validate_password(password: str):
if len(password) < 12:
raise ValueError("Password must be at least 12 characters")
if is_pwned(password):
raise ValueError("Password found in known data breach")
检查项:
- 强制执行最小长度(NIST 800-63b 建议 ≥8;建议至少 12)
- 检查密码是否已知遭泄露(使用 HaveIBeenPwned API 或本地列表)
- 不要强制定期更换密码(NIST 800-63b 已不再建议这样做)
- 根据最糟糕的 10,000 个密码列表筛查密码
- 密码长度上限不得低于 64 个字符
4. 会话管理(Flask)
# ✓ GOOD — Flask-Login with secure session config
app.config.update(
SESSION_COOKIE_SECURE=True, # HTTPS only
SESSION_COOKIE_HTTPONLY=True, # no JS access
SESSION_COOKIE_SAMESITE='Lax', # CSRF mitigation
PERMANENT_SESSION_LIFETIME=timedelta(hours=1),
)
@app.route("/logout")
@login_required
def logout():
logout_user() # ✓ invalidates session
session.clear() # ✓ belt-and-suspenders
return redirect("/login")
检查项:
- 使用
Flask-Login或Flask-Security-Too(不自行编写会话管理) - 退出登录时使会话失效(清除服务端会话和 Cookie)
- 成功登录后重新生成会话 ID(防止会话固定)
- 均设置
SESSION_COOKIE_SECURE、SESSION_COOKIE_HTTPONLY和SESSION_COOKIE_SAMESITE - 会话生命周期设有上限;空闲会话会过期
5. 错误消息枚举
# ✗ BAD — reveals which field was wrong
if not user:
return {"error": "Username not found"}
if not verify_password(password, user.hashed_password):
return {"error": "Incorrect password"}
# ✓ GOOD — generic message regardless of failure reason
if not user or not verify_password(password, user.hashed_password):
return JSONResponse({"error": "Invalid credentials"}, status_code=401)
检查项:
- 身份验证失败时始终返回相同的消息:
"Invalid credentials"(或同等表述) - HTTP 状态码保持一致:用户不存在时,不要返回
404,而密码错误时返回401 - 密码重置流程不会确认邮箱是否已注册
- 响应时间保持恒定(使用
hmac.compare_digest,并且即使用户不存在也始终进行哈希计算)
6. 默认凭据与硬编码密钥
# ✗ BAD — Bandit will flag these
ADMIN_PASSWORD = "admin"
TEST_USER = {"username": "test", "password": "test123"}
JWT_SECRET = "supersecretkey"
# ✓ GOOD
JWT_SECRET = os.environ["JWT_SECRET"] # loaded from environment / secrets manager
检查项:
- 运行 Bandit:
bandit -r . -t B105,B106,B107(检查硬编码密码) - 配置、种子数据或测试固件中不得出现
admin/admin、test/test、root/root等组合 - 所有密钥均从环境变量或密钥管理器(Vault、AWS SSM)加载
- 通过
.gitignore将.env文件排除在版本控制之外
7. 强制执行 MFA
检查项:
- 为所有用户账户提供 MFA;对管理员/特权角色强制启用 MFA
- 无法通过篡改 URL 参数或会话状态绕过 MFA
- TOTP 验证码只能使用一次,并在 30 秒后过期(标准 TOTP)
- 恢复码只能使用一次,并以哈希值存储
关键 CWE 参考
| CWE | 名称 | 典型发现 |
|---|---|---|
| CWE-287 | 身份验证不当 | 缺少身份验证检查、身份验证可被绕过 |
| CWE-307 | 身份验证尝试不受限制 | 登录端点未设置速率限制 |
| CWE-384 | 会话固定 | 登录后未重新生成会话 ID |
| CWE-521 | 密码要求薄弱 | 未强制执行最低长度或复杂度要求 |
| CWE-798 | 硬编码凭据 | 源代码中包含机密信息 |
| CWE-613 | 会话过期机制不足 | 注销后会话仍然有效;未设置超时 |
自动化扫描命令
# Static analysis — catches CWE-798, weak crypto, debug mode
bandit -r . -ll -ii
# Specifically target hardcoded credentials
bandit -r . -t B105,B106,B107,B108
# Dependency vulnerabilities (may expose auth libraries with known CVEs)
pip-audit
safety check
# Check JWT library versions for known vulns
pip show python-jose PyJWT authlib
修复优先级顺序
- 严重: JWT 算法混淆 / 接受
"none"→ 立即修复 - 严重: 登录未设置速率限制 → 立即添加
slowapi/Flask-Limiter - 高: 源代码中硬编码机密信息 → 移至环境变量
- 高: 注销时未使会话失效 → 修复会话管理
- 中: 通过错误消息枚举用户名 → 统一错误响应
- 中: 未检查密码是否出现在已泄露密码库中 → 集成 HaveIBeenPwned API
- 低: 缺少 MFA 选项 → 规划推出方案,优先面向特权账户
参考资料
- OWASP A07:2025 完整规范:https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/
- NIST SP 800-63b(数字身份指南):https://pages.nist.gov/800-63-3/sp800-63b.html
- HaveIBeenPwned API:https://haveibeenpwned.com/API/v3
评论
支持 Markdown