核心问题

如果有人故意滥用系统,会发生什么?

很多工程设计默认用户是善意的。

但真实系统里,用户不总是按照产品预期行动:

  • 有人会刷接口
  • 有人会撞库
  • 有人会薅优惠
  • 有人会批量注册
  • 有人会发垃圾内容
  • 有人会绕过权限
  • 有人会诱导系统给出不该给的结果
  • 有人会利用退款、邀请、积分、试用机制套利

安全与滥用防护的第一原则是:

系统不仅要服务好人,也要防止坏人把系统变成武器。

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,
})

这里 accountIdprice 都不应该由客户端决定。

更好的做法:

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:规则会被套利

任何激励机制都会被研究。

例如:

  • 邀请奖励
  • 免费试用
  • 优惠券
  • 退款策略
  • 积分系统
  • 排行榜
  • 推荐返现

设计这些功能时,要问:

  1. 用户能不能批量创建账号套利?
  2. 能不能邀请自己?
  3. 能不能退款后保留权益?
  4. 能不能重复领取优惠?
  5. 能不能伪造学习进度?
  6. 能不能刷评论或评分?

防护方式:

  • 领取限制
  • 设备和支付方式关联
  • 风险评分
  • 人工审核
  • 延迟结算
  • 异常检测
  • 可撤销奖励
  • 审计日志

安全默认值

安全应该是默认行为。

例如:

  • 默认私有
  • 默认最小权限
  • 默认拒绝未知输入
  • 默认不信任客户端
  • 默认不开启危险功能
  • 默认记录高风险操作
  • 默认短期 token
  • 默认需要重新认证高风险操作

不要让用户或管理员必须主动打开安全。

核心句:

默认值是系统伦理的一部分。

滥用防护检查清单

设计功能时问:

  1. 这个能力能否被批量调用?
  2. 是否涉及钱、权限、数据、内容或身份?
  3. 客户端传来的哪些字段不可信?
  4. 重复请求是否安全?
  5. 是否需要限流?
  6. 是否需要幂等键?
  7. 是否需要审核或延迟生效?
  8. 是否有异常检测?
  9. 是否有审计日志?
  10. 如果被滥用,如何止损和回滚?

小结

  1. 系统不仅要服务善意用户,也要防止恶意使用。
  2. Abuse case 是反向用户故事。
  3. 客户端传来的任何东西都不能直接信。
  4. 限流是安全边界,不只是性能工具。
  5. 高价值操作必须幂等。
  6. 格式正确不代表业务合法。
  7. Webhook 是输入,不是真相本身。
  8. 任何激励机制都会被套利。
  9. 安全默认值是系统伦理的一部分。