mcpField
A
authentication-failures

authentication-failures

github.com/scholarly360安全2248 次安装29 周前更新
代码仓库

关于 authentication-failures

Authentication Failures 是面向 FastAPI 和 Flask 应用的认证安全审查技能,依据 OWASP A07:2025 检查常见认证与会话风险。它提供逐项测试清单、扫描命令和按优先级排列的修复方向。

主要功能

  1. 01检查 JWT 配置与声明验证
  2. 02审查认证端点的速率限制
  3. 03检查密码策略及泄露密码筛查
  4. 04审查会话管理与错误信息
  5. 05提供自动扫描命令和修复优先级

适用场景

  1. 01审查 FastAPI 或 Flask 登录流程
  2. 02检查 JWT 验证与会话失效设置
  3. 03排查硬编码凭据和弱密码策略
  4. 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

修复优先级顺序

  1. 严重: JWT 算法混淆 / 接受 "none" → 立即修复
  2. 严重: 登录未设置速率限制 → 立即添加 slowapi/Flask-Limiter
  3. 高: 源代码中硬编码机密信息 → 移至环境变量
  4. 高: 注销时未使会话失效 → 修复会话管理
  5. 中: 通过错误消息枚举用户名 → 统一错误响应
  6. 中: 未检查密码是否出现在已泄露密码库中 → 集成 HaveIBeenPwned API
  7. 低: 缺少 MFA 选项 → 规划推出方案,优先面向特权账户

参考资料

评论

支持 Markdown