关于 broken-access-control
面向审查 Python Web 应用的安全技能,帮助开发者检查访问控制缺陷。内容涵盖缺失授权、IDOR、CORS、SSRF、CSRF 及 JWT 和会话安全,并提供审计步骤与扫描建议。
主要功能
- 01检查路由授权与权限验证
- 02核对资源所有权以发现 IDOR
- 03审查 CORS 与 SSRF 风险
- 04检查 CSRF、JWT 和会话设置
- 05提供 Bandit、OWASP ZAP 扫描建议
适用场景
- 01审计 FastAPI 或 Flask 应用
- 02检查用户资源是否存在越权访问
- 03排查跨域配置及用户输入 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)
连续两个周期位列 OWASP 风险榜首。 在 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/