逻辑漏洞通常没有明显的语法错误。页面可以正常打开,接口也能返回成功,但业务流程中的权限、顺序、金额或状态可以被绕过。
这类问题很难只靠一个通用漏洞扫描器发现,因为“正确行为”由业务规则定义。开发者需要从攻击者视角重新检查:
- 谁能调用这个功能?
- 能操作哪一条数据?
- 操作顺序能否跳过?
- 同一请求能否重复提交?
- 客户端提交的金额、角色和状态是否可信?
一、身份认证流程
认证系统是逻辑漏洞最常见的高价值目标。一次认证绕过可能直接导致管理员权限失守。
检查认证流程时,应关注:
- 用户名、密码和验证码是否都由服务端校验。
- 验证码是否与当前会话、用途和有效期绑定。
- 登录失败是否会有统一提示,避免用户枚举。
- 会话是否在登录后重新生成。
- 普通用户创建后是否仍可能访问管理功能。
- 高权限账户是否要求多因素认证。
1.1 验证码只在前端校验
如果页面通过 JavaScript 判断验证码,服务端没有再次验证,那么攻击者可以直接调用接口绕过页面。
正确流程应满足:
用户提交验证码
-> 服务端读取当前会话对应挑战
-> 检查有效期与尝试次数
-> 校验答案
-> 一次性销毁挑战
验证码不能与账户密码权限发生混淆。无论验证码是否正确,服务端都必须独立完成账户认证和角色授权。
1.2 会话固定
用户在登录前获得一个会话标识,登录成功后如果服务端继续使用同一个标识,攻击者可能提前获取它并复用。
修复方式:
- 登录成功后生成新的 Session ID。
- 作废旧的匿名会话。
- 在 Cookie 中设置安全属性。
- 退出登录时服务端主动失效会话。
二、Cookie、Session 与 Token
2.1 Cookie
Cookie 是浏览器保存数据的一种机制,不等于完整登录凭证。实际身份通常由服务端会话、签名 Token 或安全存储的数据确定。
安全属性包括:
HttpOnly:限制 JavaScript 读取。Secure:只通过 HTTPS 发送。SameSite:限制跨站请求携带范围。Path与Domain:控制发送范围。
Cookie 的值不应直接包含可信角色或权限,除非同时具备可靠的签名和完整性校验。
2.2 Session
服务端 Session 更便于撤销权限。需要防止:
- Session ID 可预测。
- 会话过期时间过长。
- 退出后会话仍有效。
- 登录后不轮换 Session ID。
- 服务端不校验角色和资源归属。
2.3 Token 与 JWT
JWT 是一种常见的令牌格式,但 JWT 本身不是安全保证。
服务端必须验证:
- 签名是否有效。
- 签发者是否符合预期。
- 受众是否匹配当前服务。
- 过期时间是否有效。
- 令牌是否已被撤销。
- 当前操作是否还需要额外权限检查。
不要把前端解析出的角色字段直接当作授权依据。
三、越权漏洞
越权是指用户能够访问本不应访问的功能或数据。权限判断必须在服务端完成,前端隐藏按钮只能改善界面体验,不能作为安全控制。
3.1 垂直越权
低权限用户直接调用高权限接口,例如普通用户请求管理员用户管理能力。
错误设计:
前端判断当前用户是管理员
-> 显示管理按钮
服务端没有再次检查角色
正确设计:
每个管理接口
-> 验证当前身份
-> 验证所需角色
-> 记录审计日志
-> 执行操作
3.2 水平越权
同一角色用户访问其他用户的资源。例如修改订单 ID 后读取别人的订单。
不安全查询:
SELECT id, amount, status
FROM orders
WHERE id = ?;
如果只检查订单存在,没有检查订单归属,攻击者可能遍历 ID。
安全查询:
SELECT id, amount, status
FROM orders
WHERE id = ?
AND user_id = ?;
参数绑定解决 SQL 注入,user_id 条件解决对象级授权。两者不能互相替代。
3.3 统一对象级权限
大型系统容易在部分接口遗漏资源归属检查。建议使用统一授权层:
身份认证
-> 功能权限
-> 资源归属
-> 字段级权限
-> 审计
查询、修改、删除和导出接口都应经过同一套规则,而不是开发者在每个 SQL 中手工判断。
四、支付与交易流程
支付逻辑不能信任客户端提交的价格、优惠、角色和订单状态。
4.1 价格篡改
客户端通常只负责展示商品价格,实际金额应在服务端重新计算。
安全流程:
- 服务端根据商品 ID 查询当前价格。
- 服务端重新计算优惠。
- 服务端验证库存和购买限制。
- 创建待支付订单。
- 支付回调验证签名和金额。
- 只有可信回调才能把订单改成已支付。
4.2 支付回调重复通知
支付平台可能多次发送相同通知。如果没有幂等处理,订单可能重复发货。
可以使用唯一支付流水号、订单状态机和幂等记录:
订单创建
-> 待支付
-> 支付确认中
-> 已支付
-> 已发货
状态只能按业务允许的路径变化,不能从“已退款”直接跳回“待发货”。
4.3 优惠券与积分
常见问题包括:
- 同一优惠券重复使用。
- 领取数量超过限制。
- 优惠金额超过订单金额。
- 退款后优惠券没有按规则恢复。
- 多设备并发领取。
服务端应使用唯一约束、事务和幂等键,而不是只依赖前端按钮禁用。
五、并发竞争
条件竞争发生在多个进程或线程同时处理同一资源时。检查与更新之间如果存在时间差,最终结果可能违反业务规则。
常见场景:
- 同一余额被并发消费。
- 同一库存被多人同时锁定。
- 优惠券被多个请求同时兑换。
- 同一订单被重复取消或退款。
- 竞争条件下创建多个唯一账户。
5.1 原子更新
不安全思路是先查询余额,再在应用层判断,最后写入。多个请求可能同时读到旧值。
更安全的方式是让数据库在一条语句中同时检查条件和扣减:
UPDATE accounts
SET balance = balance - ?
WHERE id = ?
AND balance >= ?;
执行后必须检查影响行数。如果影响行数为零,说明余额不足或记录状态已变化。
5.2 事务与锁
涉及多条记录时,可以使用事务和行锁:
START TRANSACTION;
SELECT stock
FROM products
WHERE id = ?
FOR UPDATE;
UPDATE products
SET stock = stock - ?
WHERE id = ?
AND stock >= ?;
COMMIT;
不同数据库的锁语义、隔离级别和超时行为不同。实现前需要确认业务允许的并发模型。
5.3 幂等键
支付、下单和兑换接口可以要求客户端提交幂等键:
idempotency_key
-> 唯一约束
-> 保存请求结果
-> 重复请求返回同一结果
幂等键应绑定用户和业务类型,不能由攻击者随意覆盖其他用户的记录。
六、文件与路径逻辑
上传接口除了防止 Webshell,还要保证文件归属于正确用户。
需要检查:
- 用户是否能下载他人的文件。
- 文件名是否可能造成路径穿越。
- 下载接口是否只用对象 ID 查询。
- 私有文件是否放在公开目录。
- 分享链接是否有有效期和权限限制。
- 删除文件时是否检查所有者。
安全查询示例:
SELECT storage_key, mime_type, original_name
FROM user_files
WHERE id = ?
AND owner_id = ?
AND deleted_at IS NULL;
服务端拿到内部 storage_key 后才读取文件,不直接使用用户提交的路径。
七、状态机与流程绕过
多步骤流程需要防止跳步、重复和乱序。
例如找回密码:
提交账户
-> 发送一次性令牌
-> 校验令牌
-> 设置新密码
-> 作废令牌和旧会话
需要确保:
- 令牌一次性使用。
- 令牌有明确有效期。
- 令牌只适用于当前用户和用途。
- 设置密码后旧会话失效。
- 不能通过修改步骤编号跳过验证。
服务端应保存流程状态,而不是依赖前端页面顺序。
八、测试方法
逻辑漏洞测试应以完整业务链路为单位,而不是只测试单个参数。
8.1 权限矩阵
列出角色和操作:
| 操作 | 普通用户 | 编辑 | 管理员 |
|---|---|---|---|
| 查看自己的文章 | 允许 | 允许 | 允许 |
| 编辑自己的文章 | 允许 | 允许 | 允许 |
| 编辑他人的文章 | 禁止 | 按业务 | 允许 |
| 管理用户 | 禁止 | 禁止 | 允许 |
针对每一格测试正面和负面用例。
8.2 对象级测试
准备两个测试账号,验证:
- A 能否读取 B 的订单。
- A 能否修改 B 的地址。
- A 能否删除 B 的文件。
- A 能否通过修改 ID 绕过权限。
测试数据必须属于授权的隔离环境。
8.3 并发测试
对同一资源同时发送多个请求,检查:
- 余额是否变成负数。
- 库存是否超卖。
- 优惠券是否重复使用。
- 订单是否重复发货。
- 幂等键是否真正生效。
并发测试需要控制速率,避免影响真实业务。
九、防御清单
- 所有权限判断都在服务端完成。
- 每个敏感接口检查角色和资源归属。
- 价格、数量、角色和状态由服务端确定。
- 登录成功后轮换 Session ID。
- JWT 验证签名、签发者、受众和有效期。
- 支付回调验证签名、金额和订单状态。
- 下单、支付和兑换使用幂等键。
- 余额和库存使用原子更新或事务锁。
- 文件下载检查所有者。
- 多步骤流程保存服务端状态。
- 关键操作写入审计日志。
- 权限矩阵和并发场景有自动化回归测试。
十、小结
逻辑漏洞的难点不在于代码语法,而在于业务规则是否真正由服务端执行。
可靠的设计需要同时回答三个问题:谁可以操作、能操作哪条资源、在什么状态下可以操作。只有把身份、对象所有权和状态机统一放进服务端授权与事务边界,才能减少越权、重复提交和并发竞争。