核心问题
如果有人故意滥用系统,会发生什么?
很多工程设计默认用户是善意的。
但真实系统里,用户不总是按照产品预期行动:
- 有人会刷接口
- 有人会撞库
- 有人会薅优惠
- 有人会批量注册
- 有人会发垃圾内容
- 有人会绕过权限
- 有人会诱导系统给出不该给的结果
- 有人会利用退款、邀请、积分、试用机制套利
安全与滥用防护的第一原则是:
系统不仅要服务好人,也要防止坏人把系统变成武器。
Abuse Case:反向用户故事
产品设计常写 user story:
作为学习者,我想购买课程,以便开始学习。
安全设计要补 abuse case:
作为攻击者,我想批量注册账号领取试用,以便免费访问付费课程。
作为恶意用户,我想重复调用退款接口,以便获得退款但保留访问权。
作为爬虫,我想批量抓取课程内容,以便复制平台内容。
Abuse case 的价值是:
把恶意路径显式化。
如果只设计正常路径,系统会在异常路径里暴露真实边界。
Trust Boundary:信任边界
安全设计要先画信任边界。
哪些输入来自不可信环境?
- 浏览器请求
- 移动端请求
- Webhook
- 第三方 API
- 上传文件
- 用户填写内容
- URL 参数
- Cookie
- localStorage
- 前端传来的 role、price、courseId
原则:
客户端传来的任何东西都不能直接信。
坏例子:
await purchaseCourse({
accountId: req.body.accountId,
courseId: req.body.courseId,
price: req.body.price,
})
这里 accountId、price 都不应该由客户端决定。
更好的做法:
const accountId = session.accountId
const course = await courses.findById(req.body.courseId)
const price = await pricing.currentPriceFor(course.id)
客户端可以表达意图,服务器必须重新验证事实。
Rate Limit:限制滥用速度
Rate limit 不是性能优化,而是安全边界。
适合限流的地方:
- 登录
- 注册
- 发送验证码
- 密码重置
- 邀请成员
- 创建订单
- 提交评论
- 上传文件
- 搜索接口
- AI 生成接口
- 导出数据
限流维度不能只有 IP。
可以组合:
- IP
- accountId
- organizationId
- device fingerprint
- email / phone
- payment method
- API key
例如:
每个手机号每 10 分钟最多 3 次验证码。
每个 IP 每小时最多 20 次注册尝试。
每个组织每天最多邀请 500 个成员。
核心句:
任何可被批量调用的能力,都需要考虑滥用速度。
Idempotency:防止重复执行
很多滥用和事故来自重复请求。
例如:
- 重复扣费
- 重复退款
- 重复发放课程访问权
- 重复发送优惠券
- 重复创建订单
支付、退款、发券、发放权益必须考虑幂等。
例如:
type IdempotencyRecord = {
key: string
accountId: string
operation: "create_purchase" | "refund_payment"
resultId?: string
createdAt: Date
}
客户端或服务端提供 idempotency key:
POST /payments
Idempotency-Key: checkout-session-123
重复请求应该返回同一个结果,而不是再次执行。
核心句:
高价值操作必须能安全重试。
Input Validation:所有输入都要验证
输入校验不是表单美化。
它是系统边界。
要验证:
- 类型
- 长度
- 格式
- 范围
- 枚举值
- 资源是否存在
- 当前用户是否有权引用该资源
例如:
assignCourse({
organizationId,
accountId,
courseId,
})
不仅要验证 ID 格式,还要验证:
- account 是否属于 organization
- course 是否属于该组织可分配范围
- 当前操作者是否能分配
- course 是否仍可分配
核心句:
格式正确不代表业务合法。
File Upload:上传是高风险入口
文件上传特别危险。
风险包括:
- 超大文件耗尽存储
- 恶意文件
- 脚本注入
- 伪装 MIME type
- 图片解析漏洞
- 隐私信息泄露
- 版权内容滥用
基本防护:
- 限制大小
- 限制类型
- 服务端检测 MIME
- 病毒扫描
- 隔离存储
- 不直接执行
- 生成安全 URL
- 图片重新编码
- 权限控制访问
不要相信文件名和客户端声明的 content type。
Webhook:第三方回调也不可信
Webhook 来自第三方,但仍然要验证。
例如支付 webhook:
必须验证:
- 签名
- 时间戳
- 事件 ID 是否处理过
- payment 是否属于本系统
- amount/currency 是否匹配
- 状态流转是否合法
坏做法:
if (event.type === "payment.succeeded") {
grantCourseAccess(event.accountId, event.courseId)
}
更好:
verifyWebhookSignature(req)
ensureEventNotProcessed(event.id)
const payment = await payments.findByProviderId(event.paymentId)
verifyPaymentAmount(payment, event.amount, event.currency)
markPaymentSucceeded(payment.id)
核心句:
Webhook 是输入,不是真相本身。
Fraud and Gaming:规则会被套利
任何激励机制都会被研究。
例如:
- 邀请奖励
- 免费试用
- 优惠券
- 退款策略
- 积分系统
- 排行榜
- 推荐返现
设计这些功能时,要问:
- 用户能不能批量创建账号套利?
- 能不能邀请自己?
- 能不能退款后保留权益?
- 能不能重复领取优惠?
- 能不能伪造学习进度?
- 能不能刷评论或评分?
防护方式:
- 领取限制
- 设备和支付方式关联
- 风险评分
- 人工审核
- 延迟结算
- 异常检测
- 可撤销奖励
- 审计日志
安全默认值
安全应该是默认行为。
例如:
- 默认私有
- 默认最小权限
- 默认拒绝未知输入
- 默认不信任客户端
- 默认不开启危险功能
- 默认记录高风险操作
- 默认短期 token
- 默认需要重新认证高风险操作
不要让用户或管理员必须主动打开安全。
核心句:
默认值是系统伦理的一部分。
滥用防护检查清单
设计功能时问:
- 这个能力能否被批量调用?
- 是否涉及钱、权限、数据、内容或身份?
- 客户端传来的哪些字段不可信?
- 重复请求是否安全?
- 是否需要限流?
- 是否需要幂等键?
- 是否需要审核或延迟生效?
- 是否有异常检测?
- 是否有审计日志?
- 如果被滥用,如何止损和回滚?
小结
- 系统不仅要服务善意用户,也要防止恶意使用。
- Abuse case 是反向用户故事。
- 客户端传来的任何东西都不能直接信。
- 限流是安全边界,不只是性能工具。
- 高价值操作必须幂等。
- 格式正确不代表业务合法。
- Webhook 是输入,不是真相本身。
- 任何激励机制都会被套利。
- 安全默认值是系统伦理的一部分。