关于 insecure-design
insecure-design 是针对 OWASP A06:2025 的安全审查技能,帮助分析架构层面的安全控制缺失。它通过检查清单、STRIDE 威胁建模和修复优先级,覆盖限流、文件上传、租户隔离等设计问题。
主要功能
- 01识别架构层面的安全设计缺陷
- 02检查认证端点限流和文件上传
- 03审查业务逻辑及租户隔离
- 04评估服务端与客户端安全控制
- 05使用 STRIDE 指引威胁建模
适用场景
- 01审查 FastAPI 或 Flask 应用的安全设计
- 02检查登录和支付流程的限流控制
- 03评估多租户查询的隔离措施
- 04在开发前使用 STRIDE 梳理威胁
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ scholarly360/ owasp-top10-web-skills --skill insecure-design安装后在 Claude Code 中输入/insecure-design确认已加载。
附带文件references/cwe-details.mdreferences/threat-modeling.md
详细介绍
OWASP A06:2025 — 不安全设计
此技能的作用
指导检测、分析和修复不安全设计漏洞——这类漏洞属于架构层面的缺陷,表现为缺少安全控制或安全控制在结构上不足。与实现错误不同,仅靠修补代码无法修复不安全设计;必须重新设计流程或添加缺失的控制层。
OWASP 2025 排名: 第 6 位(较 2021 年的第 4 位下降,原因是威胁建模的采用率有所提高)
涵盖的 CWE: 39 | 平均发生率: 1.86%
主要 CWE: CWE-434、CWE-269、CWE-362、CWE-799、CWE-602
核心概念
设计与实现
| 不安全设计(A06) | 实现错误(其他类别) |
|---|---|
| 登录流程的架构中从未加入速率限制 | 已实现速率限制器,但配置错误 |
| 按设计将文件上传到 Web 根目录 | 上传处理程序缺少扩展名检查 |
| 多租户数据库架构中没有租户隔离 | 查询缺少 WHERE 子句 |
| 业务逻辑允许负数量 | 缺少服务器端输入验证 |
设计缺陷需要添加新的控制或重构架构,而不仅仅是修补现有代码。
FastAPI / Flask 测试清单
1. 速率限制(CWE-799)
确认登录、注册、密码重置和高价值交易端点均实施速率限制。
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")
async def login(request: Request, ...): ...
Flask — Flask-Limiter:
from flask_limiter import Limiter
limiter = Limiter(app, key_func=get_remote_address)
@app.route("/login", methods=["POST"])
@limiter.limit("5 per minute")
def login(): ...
审查时需标记的风险信号:
- 登录、
/register、/reset-password、/checkout路由没有速率限制装饰器 RATELIMIT_ENABLED = False,或初始化了限制器,却未将其应用于敏感路由- 速率限制过于宽松(例如,为身份验证端点设置
1000/minute)
2. 文件上传安全(CWE-434)
文件上传必须按内容进行验证,而不能只检查扩展名,并且必须存储在Web 根目录之外。
import magic # python-magic
ALLOWED_MIME = {"image/jpeg", "image/png", "application/pdf"}
MAX_SIZE = 5 * 1024 * 1024 # 5 MB
@app.post("/upload")
async def upload(file: UploadFile):
content = await file.read(MAX_SIZE + 1)
if len(content) > MAX_SIZE:
raise HTTPException(400, "File too large")
mime = magic.from_buffer(content[:2048], mime=True)
if mime not in ALLOWED_MIME:
raise HTTPException(400, "Unsupported file type")
# Store in /var/app/uploads/ — NOT inside static/ or public/
safe_path = UPLOAD_DIR / secrets.token_hex(16)
safe_path.write_bytes(content)
风险信号:
- 仅依赖
file.content_type或扩展名(filename.endswith(".jpg")) - 将上传文件存储在
static/、public/或任何可通过 Web 访问的路径中 - 没有文件大小上限
- 未经身份验证即可通过直接 URL 提供上传文件
3. 业务逻辑缺陷
业务逻辑缺陷因应用而异——它们利用有效流程,以非预期的方式达成目的。
常见测试模式:
| 场景 | 测试 |
|---|---|
| 购物车中的负数量 | 使用 quantity: -1 发送 POST 请求,确认订单总额不会变成负数 |
| 篡改价格 | 使用 price 字段发送 POST 请求,确认服务器忽略客户端提供的价格 |
| 并发购买竞争条件 | 并行请求购买最后一件商品,确认只有一个请求成功 |
| 滥用预约系统 | 并行预订同一时段两次 |
| 重复使用优惠券 | 在第一笔交易提交前重复使用同一张优惠券 |
FastAPI 示例 — 安全处理数量:
class OrderItem(BaseModel):
product_id: int
quantity: int = Field(..., ge=1, le=100) # enforced server-side
风险信号:
- 接受客户端负载中的价格、折扣或角色字段
- 金融交易没有幂等键或重复检查
- 未在 Pydantic 或同类机制中使用
ge=1限定数量或金额
4. 租户隔离(CWE-269)
在多租户应用中,每个数据库查询都必须限定为经过身份验证的租户。
应实施的模式:
# UNSAFE — returns data across all tenants
def get_reports(db: Session):
return db.query(Report).all()
# SAFE — scoped to current tenant
def get_reports(db: Session, current_user: User):
return db.query(Report).filter(Report.tenant_id == current_user.tenant_id).all()
风险信号:
- 任何未按租户或用户进行筛选的
db.query(Model).all() - 可从面向用户的端点访问类似管理员的查询
- 在请求正文中传递租户 ID(必须从 JWT/会话中获取,而不能来自用户输入)
5. 仅在客户端实施服务器端安全控制(CWE-602)
仅在浏览器中实施的安全控制很容易通过直接 API 调用绕过。
风险信号:
- 只在 JavaScript / 前端中进行授权检查,而未在 API 路由中检查
- 控制价格、角色或访问级别的隐藏表单字段
- 基于客户端提供的值设置功能标志(应使用 JWT 声明或服务器会话)
# WRONG — trusting client-supplied role
@app.post("/admin/action")
async def admin_action(role: str = Body(...)): # never do this
if role == "admin":
...
# CORRECT — derive from authenticated token
@app.post("/admin/action")
async def admin_action(current_user: User = Depends(get_current_user)):
if current_user.role != "admin":
raise HTTPException(403)
...
6. 凭据 / 账户恢复设计
不安全的恢复流程是设计缺陷,而非实现错误。
危险信号:
- 基于知识的安全问题(母亲的娘家姓、宠物名字)——可以从社交媒体上猜到
- 密码重置链接在 15–60 分钟内未过期
- 重置令牌通过 HTTP 发送或未经过哈希处理便存储
- 可通过重置流程枚举账户(提示“未找到账户”与静默成功的区别)
安全模式:
# Generate short-lived, single-use, high-entropy token
token = secrets.token_urlsafe(32)
expiry = datetime.utcnow() + timedelta(minutes=30)
# Store hash of token, not the token itself
db_token = PasswordResetToken(
user_id=user.id,
token_hash=hashlib.sha256(token.encode()).hexdigest(),
expires_at=expiry,
used=False
)
威胁建模指南(STRIDE)
使用 STRIDE,在设计缺陷进入代码之前将其找出:
| 威胁 | 适用对象 | FastAPI/Flask 示例 |
|---|---|---|
| S 欺骗 | 身份验证流程 | 可预测的会话令牌、缺少 MFA |
| T 篡改 | 传输中 / 存储中的数据 | 签名数据缺少 HMAC、上传文件可写 |
| R 抵赖 | 审计日志 | 未记录财务操作的请求日志 |
| I 信息泄露 | 错误消息、API 文档 | 生产环境中 /docs 公开、500 错误中包含堆栈跟踪 |
| D 拒绝服务 | 速率限制、资源使用 | 未限制请求大小、缺少速率限制器 |
| E 权限提升 | 授权设计 | 角色由客户端提供、缺少租户范围 |
修复优先级矩阵
| 发现项 | 严重性 | 修复复杂度 |
|---|---|---|
| 身份验证 / 支付端点未设置速率限制 | 高 | 低 — 添加 SlowAPI/Flask-Limiter |
| 文件上传存储在 Web 根目录中 | 高 | 中 — 移动存储路径,添加服务层 |
| 价格 / 角色字段由客户端提供 | 严重 | 低 — 从请求模型中移除字段 |
| 数据库查询缺少租户范围 | 严重 | 中 — 审查所有查询 |
| 基于知识的安全问题 | 中 | 高 — 重新设计恢复流程 |
| 库存 / 预订存在竞态条件 | 中 | 高 — 添加数据库级锁定或幂等性 |
参考资料
如需深入了解,请参阅:
references/cwe-details.md— A06 中全部 39 个 CWE 的完整说明和示例references/threat-modeling.md— FastAPI/Flask 应用的 STRIDE 工作表模板
与设计问题相关的 OWASP 类别:
- A01 访问控制失效 — 设计默认授予过多访问权限时
- A07 身份验证失效 — 身份验证流程设计是根本原因时
- A10 特殊情况处理不当 — 错误恢复设计未能采取失败关闭策略时