导语:扫描器扫不出来的漏洞
业务逻辑漏洞(Business Logic Vulnerability)有一个共同特征:代码"技术上没有漏洞",但业务规则被利用。价格改到 0.01 也能正常下单、换个 ID 就能看别人的订单、积分越刷越多——扫描器对此完全无能为力,因为它们不懂业务。
一句话总结: 业务逻辑漏洞 = “功能是对的,规则是错的”。攻击者不是攻破技术防线,而是把业务规则往极端方向推——防御靠的是「把业务规则本身变成安全规则」。
1. 越权漏洞:业务逻辑漏洞的王者
1.1 水平越权 vs 垂直越权
| 类型 | 定义 | 示例 |
|---|---|---|
| 水平越权(IDOR) | 同级别用户访问他人数据 | 改订单 ID 看别人订单 |
| 垂直越权 | 低权限用户执行高权限操作 | 普通用户调用管理员接口 |
水平越权经典代码:
// ❌ 只按传入 id 查询,不校验归属
Order order = orderService.getById(request.getParameter("orderId"));
// 把 orderId 换成别人的 → 返回别人的订单
垂直越权经典代码:
// ❌ 前端隐藏按钮,后端却不鉴权
@GetMapping("/admin/users")
public List<User> listUsers() { ... } // 任何登录用户都能调用
一句话总结: 越权的根因是「后端没有做基于数据归属与角色的访问控制」,前端隐藏只是遮羞布——后端必须对每一次数据访问校验「我有权看这条数据吗」。
1.2 越权测试方法论
① 抓包改 ID:水平越权核心手法——把请求里的 ID/标识换成他人/相邻值
② 改角色字段:请求体/响应里手动加 role=admin、isAdmin=true 看后端是否信
③ 枚举接口:遍历 /api/order/1 /api/order/2 ... 看是否返回他人数据
④ 方法级权限测试:低权限 token 调高权限接口(POST /admin/...)看是否 403
⑤ 批量接口:关注导出、搜索、批量操作是否漏校验归属
2. 金额与数量类篡改
2.1 典型场景
| 场景 | 攻击手法 |
|---|---|
| 下单金额 | 篡改商品单价 / 总价字段 |
| 支付 | 改支付金额、改币种、跳过支付回调 |
| 退款 | 构造负数退款、重复退款 |
| 积分/余额 | 篡改积分增减、负数冲值 |
| 优惠券 | 叠加用券、多次领券、改优惠金额 |
篡改链示例:
① 前端表单 price=99 → 攻击者改成 price=0.01 → 后端直接按字段入账 ❌
② 下单接口返回「待支付 99」→ 攻击者调用「确认支付」接口不带金额 → 后端默认按 0 收 ❌
③ 退款接口:退 100,攻击者传 -100 → 余额反而 +100 ❌
正确姿势:
金额一律服务端计算:商品价格从商品表取,下单时服务端重算合计
支付以支付回调/服务端账单为准,不信任客户端传来的金额
退款/增减的入账操作对「原始单据」做幂等校验(不能重复退同一单)
一句话总结: 凡是"钱"和"数量",服务端必须自己算,客户端传来的金额一律只当参考、不当依据。
3. 竞态条件(Race Condition)类漏洞
3.1 竞态的本质
多个并发请求同时执行"检查-执行"逻辑,在检查通过后、执行完成前,另一个请求也通过了检查——导致一次额度被用了两次、一张券领了多份。
经典竞态(领券):
if (coupon.remaining > 0) { // 检查
coupon.remaining--; // 执行
user.coupons.add(coupon); // 执行
}
并发 100 个请求同时通过检查 → 剩下 0 张却发出去 100 张
更隐蔽的竞态:多阶段接口(创建订单 → 支付 → 发货),
攻击者并发调用中间阶段,跳过前置校验。
3.2 防御
① 数据库原子操作:UPDATE ... WHERE remaining > 0(把检查放进 SQL 条件里)
② 唯一约束兜底:唯一索引/唯一业务单号(同一个人同一张券只能有一行)
③ 分布式锁/Redis 锁:对同一业务实体(用户+券)加锁串行化
④ 幂等键:接口带幂等 key,服务端按 key 去重
⑤ 乐观锁/版本号:UPDATE ... WHERE version = ?
一句话总结: 竞态漏洞只靠代码"看起来对"永远测不出来,必须靠原子性(SQL 条件更新 + 唯一约束 + 锁)在存储层兜底。
4. 验证与流程绕过
4.1 常见绕过点
① 验证码/风控跳过:前端发了验证码,后端不校验;或「验证接口」与「业务接口」分离,
攻击者直接调业务接口跳过验证
② 步骤顺序绕过:先做完「支付」再跳「下单」,或直接跳到「发货」
③ 审批绕过:构造非正常状态(如直接把状态改成已审核)
④ 次数限制绕过:验证码发送次数、登录失败次数只在前端限制
⑤ 时间/条件绕过:黑名单校验只在特定时段生效、白名单只校验首字符
4.2 防御设计原则
状态机校验:业务流程用服务端状态机驱动,每一步校验「当前状态是否允许此动作」
例如:订单状态枚举 NEW → PAID → SHIPPED → DONE,
只有 PAID 才能 SHIPPED,后端校验状态迁移,而非信任前端传入状态
全链路校验:验证码、风控、审批的校验点放在「执行业务动作的接口」上,
而不是放在「独立的校验接口」上
服务端状态权威:所有状态变更由服务端计算并落库,前端只读展示
一句话总结: 流程绕过的最佳解药是服务端状态机——让"合法状态迁移"成为唯一路径,前端传什么状态都不被信任。
5. 为什么扫描器测不出来
自动化工具擅长检测「协议/语法层」问题(注入、XSS),
业务逻辑漏洞依赖「领域知识」:
· 什么是合理的价格?工具不知道
· 谁有权看这条数据?工具不知道
· 一次最多领几张券?工具不知道
结论:业务逻辑漏洞主要靠「人工测试 + 威胁建模」发现,
重点是测试人员(或安全人员)真正理解业务规则。
一句话总结: 发现业务逻辑漏洞的前提是"懂业务"——把业务流程画成状态机、把每个业务规则当作攻击面,逐个问"如果极端化会怎样"。
6. 防御与评审清单
6.1 安全评审检查项
① 访问控制:每个接口/数据访问是否做了「归属 + 角色」校验?(不只登录校验)
② 金额/数量:是否服务端计算?客户端金额是否只当参考?
③ 幂等性:写操作是否幂等?重复请求会重复扣款/发券吗?
④ 状态机:业务流程是否有服务端状态迁移校验?
⑤ 竞态:检查-执行之间是否原子?
⑥ 限额:领券/抽奖/验证码是否有服务端限额 + 唯一约束?
⑦ 越权枚举:所有按 ID 查询的接口,是否校验归属?
6.2 设计阶段预防
在威胁建模与需求评审阶段就加入「业务规则安全设计」:
· 每个业务规则标注「如果被滥用,最坏后果是什么」
· 金额、库存、额度、积分、券 → 一律服务端权威计算
· 所有跨用户数据访问 → 统一走「资源归属校验」中间件
· 高风险写操作 → 幂等键 + 审计日志
一句话总结: 业务逻辑安全的成本最低点在设计期——把「校验归属、服务端算钱、状态机驱动、幂等兜底」写成强制规范,比事后补洞便宜一个数量级。
7. 避坑清单
| 坑 | 后果 | 对策 |
|---|---|---|
| 后端不校验数据归属 | 水平越权大面积泄露 | 资源归属校验中间件 |
| 信任前端传的金额 | 1 分钱下单 | 金额服务端计算 |
| 检查-执行无原子性 | 竞态刷券/重复退款 | 条件更新 + 唯一约束 |
| 状态在前端/直接信任 | 跳过支付直接发货 | 服务端状态机 |
| 验证与业务接口分离 | 绕过验证直接调业务 | 校验放在执行业务的接口 |
| 只做前端隐藏 | 直接调接口即越权 | 后端强制鉴权 |
8. 总结
业务逻辑漏洞的根源是「安全规则没有融入业务规则」。记住四句防御口诀:
| 场景 | 口诀 |
|---|---|
| 越权 | 后端校验「归属 + 角色」,前端隐藏不算数 |
| 金额/数量 | 服务端计算,客户端金额只当参考 |
| 竞态 | 检查-执行必须原子,唯一约束兜底 |
| 流程 | 服务端状态机,前端传状态不信任 |
测试记住:先画业务流程状态机,再对每个节点问"能跳过吗、能极端化吗、能并发吗"。把业务逻辑漏洞纳入常规安全评审与威胁建模,而不是指望扫描器——这往往是渗透测试里"最有价值漏洞"的来源。
延伸阅读
- OWASP Top 10 深度解析 — 失效的访问控制与业务逻辑定位
- 威胁建模与安全评审清单 — 设计期发现业务逻辑风险
- 渗透测试与红蓝对抗 — 业务逻辑漏洞的实战测试手法
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。